爲 Java 開發者準備的 Go 教程

大家好,我是 polarisxu。

在正式工作之前,一直使用 Java,雖然這些年對 Java 的關注變少了,但很顯然,Java 用戶羣體特別大。不過,我也知曉,有不少 Java 用戶在學 Go。我嘗試寫一系列文章,爲 Java 開發者講解 Go 語言。

這是第一篇,從大的層面簡單對比下 Go 和 Java,算作是一次漫遊。


整體上,Java 和 Go 之間有許多明顯而微妙的區別,包括語言層面和運行時層面。我們這裏主要在語言層面漫遊。

這裏的比較,沒有貶低哪門語言的意思,旨在客觀指出各自的特點。

Go 和 Java 都是 C 系語言,但 Go 更接近 C,包括風格、庫等。

01 Go 是編譯型語言,而 Java 是半解釋的

與 C/C++ 一樣,Go 語言源碼會直接編譯成目標計算機體系結構的機器語言。而 Java 源碼編譯成虛擬機語言,即字節碼,並由 Java 虛擬機(JVM)進行解釋(interpreted)。爲了提高性能,字節碼通常在運行時被動態編譯成機器語言。JVM 本身是爲特定的操作系統和硬件體系結構構建的。

而且 Go 是靜態編譯,一旦編譯完成,Go 程序只需要一個操作系統就可以運行。Java 程序在運行之前需要計算機上安裝有 JRE(特定版本)。許多 Java 程序可能還需要額外的第三方代碼。

所以,雖然 Go 和 Java 都是跨平臺的,但實現形式還是很不一樣。

02 Go 和 Java 程序結構類似

這兩門語言都支持包含方法和字段的數據結構的概念。在 Go 中,它們被稱爲 struct(結構體),而在 Java 中,它們被稱爲 class(類)。這些結構被收集到稱爲包(package)的分組中。在這兩門語言中,包都可以分層排列(即嵌套包)。

Java 包頂層只包含類型聲明。Go 包可以各種聲明,如變量、常量、函數以及派生類型聲明。

兩門語言都通過導入(import)來訪問不同包中的代碼。在 Java 中,可以選擇使用導入的類型(String 或 Java.lang.String)。在 Go 中,所有導入的名稱必須始終是限定的(雖然可以本地導入,但不建議使用)。

03 Go 和 Java 代碼風格的差異

這方面涉及到的內容不少,無法一一列出。這裏提一些:

1)Go 與衆不同,變量聲明,類型放在後面。語言通常省略分號。

Java:int x, y, z;

Go:var x, y, z int

2)Java 方法只能返回一個值。Go 函數 / 方法可以返回許多值。

3)Java 方法和字段必須在它們所屬的類型內聲明。Go 方法是在所屬類型之外定義的。Go 支持獨立於任何類型的函數和變量。Java 沒有真正的靜態共享變量;靜態字段只是某些類(相對於實例)的字段。Go 支持在可執行映像中分配的真正靜態(全局)變量。

4)Java 只允許其他類型(類、枚舉和接口)的類型擴展,而 Go 可以基於任何現有類型創建新類型,包括基本類型(如整數和浮點)和其他用戶定義的類型。Go 可以支持這些自定義類型中的任何一種。

5)Go 和 Java 接口的工作方式非常不同。在 Java 中,類(或枚舉)實現接口時,必須顯式指定。在 Go 中,任何類型都可以通過實現接口的方法來實現接口,即隱式實現接口,所謂的鴨子類型。

6)Java 通過 try/catch 處理異常。Go 中是 error,另外有 panic 和 recover。

04 Java 是面嚮對象語言,而 Go 不完全是

面向對象的三大特性:封裝、繼承和多態。

Go 沒有繼承的概念,認爲組合優於繼承。不過,在 Go 中,可以通過內嵌來模仿部分類似繼承的功能,但本質還是組合。

此外,Go 只在接口層面有多態,沒有方法重載。

05 Java 不少特性是基於 Annotation 的,Go 沒有 Annotation

許多 Java 庫(特別是框架,比如 Spring),都充分利用了 Java 的註解(Annotation)。註解提供元數據(通常在運行時使用),以修改庫提供的行爲。Go 沒有註解,因此缺少此功能。Go 可以使用代碼生成(go generate)獲得與註釋類似的結果。Go 有一種簡單的註解形式,稱爲 tag,可用於自定義某些庫行爲,如 JSON 或 XML 格式。

06 Go 和 Java 都使用 GC 管理內存

這兩門語言都使用 stack 和 heap 來保存數據。棧主要用於函數局部變量,堆用於其他動態創建的數據。在 Java 中,所有對象都在堆上分配。在 Go 中,堆上只分配可在函數生命週期之外使用的數據(通過逃逸分析確認)。在 Java 和 Go 中,堆都是垃圾收集的;堆對象由代碼顯式分配,但總是由垃圾收集器回收。

Java 沒有指向對象的指針的概念,只引用位於堆中的對象。Go 允許訪問指向任何數據值的指針(或地址)。在大多數情況下,Go 的指針可以像 Java 引用一樣使用。

Go 的垃圾收集實現比 Java 的更簡單,通常 Go GC 需要調優的情況較少(沒有太多選項可配置)。

07 Go 和 Java 都支持併發,但方式不同

Java 有線程(Thread)的概念。而 Go 是 Goroutine,它是由語言提供的。Goroutine 通常被稱爲輕量級線程。Go 運行時支持使用比 JRE 支持的線程多得多的 Goroutine。

Java 支持同步控制。Go 具有類似的庫函數。Go 和 Java 都支持跨 Thread/Goroutine 安全更新的原子值的概念。兩者都支持顯式鎖定庫。

Go 提供了通信順序進程(CSP)的概念,作爲 Goroutine 在沒有顯式同步和鎖定的情況下進行交互的主要方式。Goroutine 通過 channel 進行通信,channel 是有效的管道(FIFO 隊列),通常與 select 語句相結合使用。

08 Go 運行時比 JRE 更簡單

Go 的運行時比 JRE 提供的運行時小得多。沒有 JVM 等價物,但兩者中都存在類似的組件,如垃圾收集。Go 沒有字節碼解釋器。

Go 擁有大量的標準庫。Go 社區提供了更多庫。但是 Java 標準庫和社區庫在功能的廣度和深度上都遠遠超過了當前的 Go 庫(畢竟 Java 比 Go 早太多年了,而且生態確實好)。儘管如此,Go 庫仍然足夠豐富,可以開發許多有用的應用程序,特別是服務端程序。

所有使用過的庫都會嵌入到 Go 可執行文件中,即前面提到的靜態編譯。可執行文件是運行程序所需的一切(而 Java 庫在首次使用時動態加載)。這使得 Go 程序二進制文件通常比 Java 二進制文件(單個 “main” 類)大,但當加載 JVM 和所有依賴類時,Java 的總內存佔用通常更大。

09 Go 程序的構建過程是不同的

Java 程序是在運行時構造的類的合併,通常來自多個源(供應商)。這使得 Java 程序非常靈活,特別是當通過網絡下載時。Go 程序是在執行之前靜態構建的。啓動時,可執行映像中的所有代碼都可用。這提供了更大的完整性和可預測性,部署特別方便,但犧牲了一些靈活性。這使得 Go 更適合集裝箱化部署。

Go 程序通常由 “go builder” 構建,這是一種結合了編譯器、依賴項管理器、鏈接器和可執行構建器等的工具。它包含在標準 Go 安裝中。Java 類被單獨編譯(由 Java 開發工具包(JDK)提供的 javac 工具編譯),然後通常被組裝成包含相關類的歸檔文件(JAR/WAR)。程序是從一個或多個存檔中加載的。檔案的創建,特別是包括任何依賴項,通常由獨立於標準 JRE 的程序(如 Maven)完成。


其他方面,比如 Java 和 Go 都是過程式語言,此外,在函數式編程方面,Go 一開始,函數就是一等公民,而 Java 8 纔有較好的支持。

參考

這個系列主要參考以下資料:

我是 polarisxu,北大碩士畢業,曾在 360 等知名互聯網公司工作,10 多年技術研發與架構經驗!2012 年接觸 Go 語言並創建了 Go 語言中文網!著有《Go 語言編程之旅》、開源圖書《Go 語言標準庫》等。

堅持輸出技術(包括 Go、Rust 等技術)、職場心得和創業感悟!歡迎關注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/pWKpEEC2fus0dXivwUC3MQ