為 Java 開發(fā)者準備的 Go 教程
閱讀本文大概需要 9 分鐘。
大家好,我是 polarisxu。
在正式工作之前,一直使用 Java,雖然這些年對 Java 的關注變少了,但很顯然,Java 用戶群體特別大。不過,我也知曉,有不少 Java 用戶在學 Go。我嘗試寫一系列文章,為 Java 開發(fā)者講解 Go 語言。
這是第一篇,從大的層面簡單對比下 Go 和 Java,算作是一次漫游。
整體上,Java 和 Go 之間有許多明顯而微妙的區(qū)別,包括語言層面和運行時層面。我們這里主要在語言層面漫游。
這里的比較,沒有貶低哪門語言的意思,旨在客觀指出各自的特點。
Go 和 Java 都是 C 系語言,但 Go 更接近 C,包括風格、庫等。
01 Go 是編譯型語言,而 Java 是半解釋的
與 C/C++ 一樣,Go 語言源碼會直接編譯成目標計算機體系結構的機器語言。而 Java 源碼編譯成虛擬機語言,即字節(jié)碼,并由 Java 虛擬機(JVM)進行解釋(interpreted)。為了提高性能,字節(jié)碼通常在運行時被動態(tài)編譯成機器語言。JVM 本身是為特定的操作系統(tǒng)和硬件體系結構構建的。
而且 Go 是靜態(tài)編譯,一旦編譯完成,Go 程序只需要一個操作系統(tǒng)就可以運行。Java 程序在運行之前需要計算機上安裝有 JRE(特定版本)。許多 Java 程序可能還需要額外的第三方代碼。
所以,雖然 Go 和 Java 都是跨平臺的,但實現(xiàn)形式還是很不一樣。
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 沒有真正的靜態(tài)共享變量;靜態(tài)字段只是某些類(相對于實例)的字段。Go 支持在可執(zhí)行映像中分配的真正靜態(tài)(全局)變量。
4)Java 只允許其他類型(類、枚舉和接口)的類型擴展,而 Go 可以基于任何現(xiàn)有類型創(chuàng)建新類型,包括基本類型(如整數和浮點)和其他用戶定義的類型。Go 可以支持這些自定義類型中的任何一種。
5)Go 和 Java 接口的工作方式非常不同。在 Java 中,類(或枚舉)實現(xiàn)接口時,必須顯式指定。在 Go 中,任何類型都可以通過實現(xiàn)接口的方法來實現(xiàn)接口,即隱式實現(xiàn)接口,所謂的鴨子類型。
6)Java 通過 try/catch 處理異常。Go 中是 error,另外有 panic 和 recover。
04 Java 是面向對象語言,而 Go 不完全是
面向對象的三大特性:封裝、繼承和多態(tài)。
Go 沒有繼承的概念,認為組合優(yōu)于繼承。不過,在 Go 中,可以通過內嵌來模仿部分類似繼承的功能,但本質還是組合。
此外,Go 只在接口層面有多態(tài),沒有方法重載。
05 Java 不少特性是基于 Annotation 的,Go 沒有 Annotation
許多 Java 庫(特別是框架,比如Spring),都充分利用了 Java 的注解(Annotation)。注解提供元數據(通常在運行時使用),以修改庫提供的行為。Go 沒有注解,因此缺少此功能。Go 可以使用代碼生成(go generate)獲得與注釋類似的結果。Go 有一種簡單的注解形式,稱為 tag,可用于自定義某些庫行為,如 JSON 或 XML 格式。
06 Go 和 Java 都使用 GC 管理內存
這兩門語言都使用 stack 和 heap 來保存數據。棧主要用于函數局部變量,堆用于其他動態(tài)創(chuàng)建的數據。在 Java 中,所有對象都在堆上分配。在 Go 中,堆上只分配可在函數生命周期之外使用的數據(通過逃逸分析確認)。在 Java 和 Go 中,堆都是垃圾收集的;堆對象由代碼顯式分配,但總是由垃圾收集器回收。
Java 沒有指向對象的指針的概念,只引用位于堆中的對象。Go 允許訪問指向任何數據值的指針(或地址)。在大多數情況下,Go 的指針可以像 Java 引用一樣使用。
Go 的垃圾收集實現(xiàn)比 Java 的更簡單,通常 Go GC 需要調優(yōu)的情況較少(沒有太多選項可配置)。
07 Go 和 Java 都支持并發(fā),但方式不同
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 沒有字節(jié)碼解釋器。
Go 擁有大量的標準庫。Go 社區(qū)提供了更多庫。但是 Java 標準庫和社區(qū)庫在功能的廣度和深度上都遠遠超過了當前的 Go 庫(畢竟 Java 比 Go 早太多年了,而且生態(tài)確實好)。盡管如此,Go 庫仍然足夠豐富,可以開發(fā)許多有用的應用程序,特別是服務端程序。
所有使用過的庫都會嵌入到 Go 可執(zhí)行文件中,即前面提到的靜態(tài)編譯。可執(zhí)行文件是運行程序所需的一切(而 Java 庫在首次使用時動態(tài)加載)。這使得 Go 程序二進制文件通常比 Java 二進制文件(單個 “main” 類)大,但當加載 JVM 和所有依賴類時,Java 的總內存占用通常更大。
09 Go 程序的構建過程是不同的
Java程序是在運行時構造的類的合并,通常來自多個源(供應商)。這使得Java程序非常靈活,特別是當通過網絡下載時。Go 程序是在執(zhí)行之前靜態(tài)構建的。啟動時,可執(zhí)行映像中的所有代碼都可用。這提供了更大的完整性和可預測性,部署特別方便,但犧牲了一些靈活性。這使得 Go 更適合集裝箱化部署。
Go 程序通常由 “go builder” 構建,這是一種結合了編譯器、依賴項管理器、鏈接器和可執(zhí)行構建器等的工具。它包含在標準 Go 安裝中。Java 類被單獨編譯(由 Java 開發(fā)工具包(JDK)提供的 javac 工具編譯),然后通常被組裝成包含相關類的歸檔文件(JAR/WAR)。程序是從一個或多個存檔中加載的。檔案的創(chuàng)建,特別是包括任何依賴項,通常由獨立于標準 JRE 的程序(如 Maven)完成。
其他方面,比如 Java 和 Go 都是過程式語言,此外,在函數式編程方面,Go 一開始,函數就是一等公民,而 Java 8 才有較好的支持。
參考
這個系列主要參考以下資料:
Go for Java Programmers: https://talks.golang.org/2015/go-for-java-programmers.slide Java to Go in-depth tutorial:https://yourbasic.org/golang/go-java-tutorial/ Go for Java Programmers: https://www.oreilly.com/library/view/go-for-java/9781484271995/
我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網公司工作,10多年技術研發(fā)與架構經驗!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網!著有《Go語言編程之旅》、開源圖書《Go語言標準庫》等。
堅持輸出技術(包括 Go、Rust 等技術)、職場心得和創(chuàng)業(yè)感悟!歡迎關注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio
