為什麼用 Java:一個 Python 程式設計師告訴你

董老師在矽谷發表於2016-03-19

這篇文章專門給程式設計師寫的,普通讀者慎入。原作者:Kevin Sookocheff 譯者:Celia Zhen,原文點選文末連結。

每當我告訴別人我一直在用Java工作時,大家的反應都是:

“納尼!Java?為啥是Java?”

說實話,本人剛開始的時候也是同樣的反應。但是由於Java的型別安全,執行效能和堅如磐石的工具,我漸漸地開始欣賞Java。同時我注意到,現在的Java已今非昔比——它在過去的10年間穩健地改善著。

緣何是Java?

假 設每天都用Java的想法還沒有讓君噁心到食不下咽,我在此重申Java已非你所瞭解的“吳下阿蒙”了。當Python, Ruby, 和Javascript在“動態型別語言革命”™(我自己造的名詞)中大放異彩時,Java已經悄悄地借鑑了動態語言和函式式語言的很多吸引人的特性,同 時保留了讓Java和JVM晉級一流開發環境的先賢的努力成果。憑藉大約9百萬Java攻城獅的基層群體,Java仍然是世界上最受歡迎的程式語言。我們 不能僅僅因為Java的語法有一點點繁瑣,就抹殺掉它所有的歷史和開發工作。但是流行不等同於正確。下面我們就來看看是什麼讓Java如此大放異彩。

Java虛擬機器(JVM)

Java虛擬機器(JVM) 已經誕生20年了。在此期間,它被部署在成千上萬的系統上,歷經了無數的漏洞修復和效能提升。JVM的優點有以下幾個方面。首先,JVM完美支援日誌和監控, 這使你可以很方便地監控小到單個執行緒的效能指標。JVM有世界上最優化的垃圾回收器之一,你可以根據優化吞吐量等因素靈活選擇垃圾回收演算法。最 後,Java承諾的“write once, run anywhere”終於得已實現——你可以輕鬆地在任何架構上部署一個Java應用(大家還是承認applet從來沒有過吧)。為什麼用Scala和 Clojure這樣新式語言的聰明人會選擇JVM作為他們的執行環境呢?——因為JVM為你的程式碼提供了一個無出其右的分發環境。拋棄像JVM這樣堅如磐 石的工具是非常不合理的。

庫的支援

如 果你需要做點什麼,很可能已經有非常好用且經過測試的Java庫在等著你。Java庫大部分都是成熟並用於實際生產開發的。Google, Amazon, LinkedIn, Twitter和很多Apache專案都很倚重於Java。如果你用了Java,你可以參考這些庫和公司,從而借鑑偉大的程式設計師先驅們的工作。

型別安全

Java的型別系統,雖然有時很繁瑣,但是這使得你可以寫出“好用”的程式碼。不再有執行除錯,它使你可以依靠編譯器而不是單元測試——單元測試只在你知道bug在哪裡的時候才有用。型別安全也使你輕鬆的程式碼重構。Java同時支援範型——Go語言的最大詬病之一。再者,Guava這樣的庫I以最小的樣板和開銷,標準化了建立型別安全的API的方法。 Java編譯器的改進也意味著你可以在享受型別安全的同時最小化範型所需的樣板程式碼。

併發性

下面這條tweet總結了大多數動態語言的並行狀態:

Most JS/Python/Ruby apps… pic.twitter.com/hkDkjdxpFH

— Reuben Bond (@reubenbond)

Java 卻有著對多執行緒和並行的一流支援。對於Java 1.7, 許並行的immutable資料結構令你輕鬆地線上程間共享資料。Akka庫更進一步的提供了Erlang型的Actors來寫併發和分散式的程式。我並 不是在說Java比Go具有更好的並行支援,但是可以管理單個執行緒這一特性為Java應用提供了非同步效能;而Python是做不到這點的。

用最新的Java來程式設計

現在你的心情可能已經從噁心變成好奇了,那麼我們在2015年該如何寫Java呢?從哪兒開始呢?首先,讓我們回顧一些在Java 7和Java 8湧現的核心語言概念。

迭代

首先我們一起來看看迭代。下面是Java 8中的 for迴圈:

List<String> names = new LinkedList<>();  // compiler determines type of LinkedList

// ... add some names to the collection

names.forEach(name -> System.out.println(name));

或者是被大大簡化的 for關鍵詞?

for (String name : names)

System.out.println(name);

這2種迴圈結構都比你平時看到的for迴圈簡潔的多。

Lambda函式

上面提到的第一個for迴圈引入了Lambda函式這個新概念。Lamda函式,語法記作->, 是Java語言的一項重大改革,並從函數語言程式設計中引入了一些概念。

下面來看幾個Java中Lambda函式的例子。

// Lambda Runnable
Runnable r2 = () -> System.out.println("Hello world two!");

// Lambda Sorting
Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()))

// Lambda Listener
testButton.addActionListener(e -> System.out.println("Click Detected by Lambda Listener"));

這裡無法詳細展開Lambda函式這個話題——http://www.drdobbs.com/jvm/lambda-expressions-in-java-8/240166764文章提供了一個很好的切入點來更多地瞭解Lambda函式。

Java 8引入了流(stream)的概念,這為Java提供了很多現代函式式語言的特性。流是一種對集合上的一系列轉換延遲執行的機制。比如我們來數一下以’A’開頭的名字。首先想到的方法肯定是像下面這樣:

List<String> names = new LinkedList<>();
// ... add some names to the collection
long count = 0;
for (String name : names)  {
if (name.startsWith("A"))
++count;
}

如果用流,上述就可以簡化為首先將集合轉換成流,然後使用函式:

List<String> names = new LinkedList<>();
// ... add some names to the collection
long count = names.stream()
.filter(name -> name.startsWith("A"))
.count();

Java同時支援用parallelStream()來進行流的並行處理。並行流允許流水線業務在獨立的執行緒同時執行,這不僅改進了語法,同時提高了效能。在大多數情況下,你可以簡單得用parallelStream()替換stream()實現並行。

 Try-With-Resources結構

在Java 6之前,開啟一個檔案然後讀取內容需要通過try/finally來完成:

static String readFirstLineFromFileWithFinallyBlock(String path)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null) br.close();
}
}

但是readLine和close都有可能丟擲異常。在這種情況下,readLine丟擲的異常被忽略,我們事實上並不知道readLine執行失敗。

Java 7引入了 Try-With-Resources結構來克服這種缺陷:

static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}

上例中,無論在何種失敗情況下,BufferedReader都會自動關閉檔案流。你可以通過用逗號分隔的方式,用一個try語句來開啟多個資源。

多重catch

以往Java只允許一個catch程式碼塊對應一個異常,這造成如下的程式碼冗餘:

catch (IOException ex) {
logger.log(ex);
throw ex;
catch (SQLException ex) {
logger.log(ex);
throw ex;
}

從Java 7開始,你可以在一個程式碼塊內捕捉多個異常,從而減少了程式碼冗餘:

catch (IOException|SQLException ex) {
logger.log(ex);
throw ex;
}

數值字面常量(Numeric Literals)

數值字面常量可以新增下劃線是Java語言的新特性。這允許你使用_作為大數字的視覺分隔符。下面的例子不言自明:

int thousand = 1_000;
int million  = 1_000_000;

使用Java

看到現代Java的語法如何簡化並擴充套件了老Java之後,你可能已經摩拳擦掌躍躍欲試Java了。我整理了一下第三方的工具和庫,這些可以用來幫助你們上手。

Maven

Maven是一個Java構建系統,相比於配置,它更重視規範。Maven定義了應用程式的結構,並提供了許多內建函式,比如執行測試,打包應用,部署你的庫。使用Maven會顯著降低管理Java專案的認知開銷。 Maven Central是Java世界中的PyPI,為已釋出的Java庫提供一站式服務。

核心函式

谷歌的Guava library提供了谷歌Java開發中所使用的核心函式。這包括應用於集合,快取,基礎資料型別,併發,字串處理工作,I/O等的常見函式。Guava為如何設計好的的Java API提供了絕佳的案例分析,提供最有效的從Java中推薦的最佳實踐的具體例子一個很好的案例, Effective Java中推薦的最佳實踐大部分都在Guava中得以體現。Guava被用於谷歌產品開發,進行了超過286,000個單元測試,可謂經受過實戰測試的考驗。

日期/時間函式

Joda-Time 已 經成為Java實際上的標準日期/時間函式庫。事實上,Java 8幾乎一字不差地採用了Joda-Time規範。自此,我們建議使用java.time中的日期/時間函式代替Joda-Time。但是,如果你需要使用 Java 8之前的版本,Joda-Time提供了無與倫比的API。

分散式系統

Akka 提供類似Erlang型的Actor模型的抽象層來編寫分散式系統。Akka可以從容應對許多種不同的故障,為編寫可靠的分散式系統提供了更高層次的抽象。

Web應用程式

需要用Java寫一個功能完善的Web應用程式?莫怕,有Play Framework罩著你。Play基於Akka的非阻塞I/O,提供了編寫Web應用程式的可擴充套件的非同步框架。如果想使用不那麼前沿但是被廣泛應用於產品的框架,請嘗試Jetty。

單元測試

JUnit 仍為編寫單元測試的標準。最近幾年,JUnit的匹配器有所擴充套件,允許你對集合作assertions。例如,您可以輕鬆地斷言一個連結串列是否包含某個特殊值。

模擬框架(Mocking Framework)

Mockito是Java的標準模擬庫。它提供了所有你能想到的且對編寫測試非常重要的模擬庫的功能。

然而不足的是。。。

目前為止,我一直在為Java說好話,但是有些方面它還是很爛。

它還是Java!

Java的歷史遺留不可避免,Java仍然向下相容其最早的版本,這意味著語言和標準庫的最爛的部分還存在著。Guava是為了令Java語言更討人喜歡而產生這個事實就證明了,Java和API存在不一致,令人困惑的問題,有時甚至是完全錯誤的。

JSON

Java缺少對映到JSON的object literal syntax(如Python的字典literal syntax)。正因如此,從Java物件對映到JSON有時需要繁複的物件例項化和對映,反之亦然。目前有各種JSON庫在這個領域競爭,Jackson是當前的最受歡迎的,但是Jackson的文件需要進一步完善。

模擬(Mocking)

Mockito解決了測試Java程式碼中的很多痛點,但是從像Python語言的靈活轉換到Java語言的嚴格,你需要更謹慎地來設計你的類用於模擬。

REPL

我之所以喜歡Python,其中一點就是它可以迅速地實現讀取﹣求值﹣輸出迴圈( read-eval-print loop),從而快速評估新的想法或檢驗假設。雖然一直有聲音說要把讀取﹣求值﹣輸出迴圈新增到標準Java庫,這一點目前還是不支援的。

語法累贅

雖然Java編譯器的進步意味著明確的型別簽名不再那麼需要——尤其對於泛型——但是Java仍然比Python冗餘的多。啟動和執行一個專案需要更多的樣板和開銷——通常這意味更多的工作。

結論

Java擁有一個漫長而傳奇的歷史,其中有好有壞。如果你已經很多年沒有使用Java工作了,也許現在是一個好機會再次嘗試它。只要不是像下面這樣做:

相關文章