Java 的新未來:逐漸“Kotlin 化”

我們行業始終有一個熱門話題,那就是對 Java 的批判,這種批判很大程度上來源於 Java 的繁瑣性以及它所生成的大量樣板式代碼 ,而這些代碼在很多場景下是根本不需要的。

雖然我一直以來都很喜歡 Java,但是我不能說這些指責是錯誤的。的確,Java 的繁瑣性以及數量不菲的雜亂代碼在很多情況下會很煩人。在大多數情況下,我們不得不接受我們生活在一個不完美的世界這一現實,很多時候,我們必須兩害相權取其輕。我們都知道,Java 並不完美,但是主要的問題在於在此之前爲什麼沒有采取任何措施來解決這些問題呢?

我認爲,變更需要耗費這麼長時間的唯一原因就是 Java 缺乏足夠的競爭。 Java 語言之所以能夠主導市場,可能就是因爲缺乏真正的競爭對手 ,當然也要歸功於 Sun 和 Oracle 先後做出的巨大努力。

Java 提供的強類型安全性,以及作爲一個結構良好的語言所帶來的其他特性,使其成爲大型項目中非常流行的語言 。通過使用 Java,我們很少會讓事情變得難以收拾。Java 的另外一個重要特點是作爲多平臺語言,它運行在自己的虛擬機上,這使其能夠完美契合很多的組織。如果你通過著名的 JIT 編譯器啓用了其自動性能優化的固有能力,那麼在很多情況下都能將糟糕代碼所帶來的影響最小化,這樣我們就有了一組使用 Java 的堅實理由。

但是,後來發生了什麼呢?接下來的事情就是,能夠像 Java 那樣運行在相同 JVM 中的新語言推向了市場,這些語言消除了 Java 中最令人頭疼的問題,併爲開發人員提供了更好的環境,而且在很多情況下,它們的學習曲線非常平坦。

在繼續下面的內容之前,我們簡要回顧一下 JVM 語言的歷史。

在開始之前,我想澄清一點,那就是我省略掉了一些現有的 JVM 語言,這主要是因爲它們從來沒有具備足夠強的吸引力,不能視爲我們行業中廣泛使用的候選語言。那麼,我們就開始快速回顧一下 JVM 語言的歷史。

Java的新未來:逐漸“Kotlin化”

圖片出自 Unsplash 站點,作者爲 Maximilian Weisbecker 

 

我們的旅程當然要從 Java 開始,它是 JVM 領域最古老和最流行的語言。

Java 語言最初是在 1996 年 1 月份正式發佈 的,所以它已經有 24 年的歷史了,怎麼樣,還不錯吧?最初,Java 是一種命令式語言,遵循純粹的面向對象程序風格。它同時也是強類型語言,Java 的語法在某種程度上與 C++  C 語言很相似,但它被認爲是一個改進版本,因爲使用 Java 編寫代碼要比使用 C 或 C++ 容易得多。另外,在它的批評者中,最大的爭議在於其繁瑣性。

第二個發佈的 JVM 語言是 Groovy ,它最早出現於 2003 年,不過其第一個官方和 標準版本 1.0 是 2007 年才發佈的 Groovy 的好處在於,它還可以用作 腳本語言 。Groovy 是一種 動態類型語言 ,所以類型檢查是在運行時進行的;這也是一些開發人員不喜歡 Groovy 的原因之一。我們使用 Groovy 編寫代碼,在編譯時看起來它是正確的,但是在運行時,我們纔會發現其中有問題。

Java的新未來:逐漸“Kotlin化”

圖片來源: 維基百科

 

接下來出現了另外一種流行的語言,也就是我們要討論的 Scala  Scala  2004 年正式發佈 ,它爲 JVM 領域帶來了一種 新的編程模型,也就是函數式編程和它的聲明式方法 。我們可以確定,Scala 率先引入了不可變性(immutability)的理念,所以它對 Java 的轉變做出了重要貢獻。但另一方面,Scala 的批評者並不喜歡它,因爲它語法複雜,可讀性一般比較差。

Java的新未來:逐漸“Kotlin化”

圖片來源: 維基百科

 

JVM 領域出現的下一個語言是 Clojure ,它是一個純函數式語言,最近變得非常流行,但是它 最早出現於 2007 年 。Clojure 是一種基於 LISP 的語言,其特點是簡單和使用純函數。在它的缺點中,值得一提的是動態類型(與 Groovy 相同),而且學習曲線更陡峭,因爲它的語法與其他 JVM 語言完全不同。如果你對學習 Clojure 感興趣的話,那麼應該閱讀如下這兩本好書:“T he joy of Clojure ”和“ Programming Clojure(第三版)”。

Java的新未來:逐漸“Kotlin化”

圖片來源: 維基百科

 

最後但同樣重要的是 Kotlin  Kotlin  2016 年 2 月份 首次發佈,從那時開始,它就變得不斷流行起來。該語言是由 JetBrains 公司設計的,它有一個明確的目標:消除 Java 中所有常見的問題。它的設計方式 保留了 Java 所有的優點 ,但是 消除了 Java 的大多數問題 ,這也是它爲何如此流行的原因,很多人甚至認爲在未來的幾年中它有可能會擊敗 Java。如果你想要了解 Kotlin 的更多內容的話(我鼓勵你這樣做,因爲它是一門偉大的語言),那麼我推薦你閱讀“ Kotlin in Action ”一書,對於 Java 開發人員來說,這是一本入門 Kotlin 的優秀圖書。

Java的新未來:逐漸“Kotlin化”

圖片出自 Unsplash 站點,作者爲 Louis Tsai 

 

這就是最重要的 JVM 語言,我們省略了一些不太流行的 JVM 語言,但是我們可以提一下它們的名字:Jython、JRuby、Ceylon、Fantom 等等。你可以通過該 地址獲取現有JVM 語言的全部列表。

我們可以發現, 在最初的八年或十年間,Java 並沒有太多的競爭對手 ,但從那之後,Java 就面臨一些競爭了,那麼有競爭是一件好事兒還是壞事兒呢?

競爭加劇帶來的好處

正如我們在前文所述,Java 在早期並沒有太多改變,這很可能是因爲沒有改變的必要,儘管它遠遠稱不上完美,但也已經得到了廣泛的使用。

但是,新的競爭者出現了,這些更現代的語言帶來了新的特性,解決了長期困擾Java 開發人員的一些痛點。

舉例來說,我們看一下 Scala 語言,自 2009 年以來,Scala 變得越來越受歡迎,開發人員歡迎這種新的函數式風格,它能夠讓他們的編碼更靈活,也能安全輕鬆地編寫出並行代碼。我們可以在下面的谷歌趨勢圖中看到這個趨勢:

Java的新未來:逐漸“Kotlin化”

圖片來源: theboreddev.com 

 

那 Oracle 對這種新趨勢的反應是什麼呢?那就是在 2014 年發佈了 Java Lambdas 和 Streams。很多人都認爲當時這是 Java 擊敗 Scala 的最大舉措。目前行業中每個人都能感受到最近幾年 Scala 因爲缺少流所遭受的損失。

JVM 領域中存在競爭者的另外一個好處就是 JIT 編譯器和 JVM 本身正在經歷的持續改進 。現在,有更多的人對 JVM 優化和性能改善感興趣。所以,競爭對每個人都是好事。

Kotlin 是這個領域最新的競爭者。Kotlin 非常重要,因爲它在一定程度上爲 Oracle 指明瞭前進方向。Kotlin 表明它能夠保持 Java 好的組成部分,同時創建一個更簡潔和快速的編碼語言。

如果看一下谷歌趨勢的話,我們能夠看到在過去幾年間,Kotlin 的流行程度:

Java的新未來:逐漸“Kotlin化”

圖片來源: theboreddev.com 

 

從圖中可以看出,Kotlin 迅速變得非常流行,但是在最近幾年,它似乎穩定了下來。

Oracle 很好地記錄了業界對 Kotlin 的反應,如果你看一下 JDK 15 的發佈說明,就會發現 Java 的一些新特性基本上就是對 Kotlin 變更的複製。例如,新的 Java record  文本塊 (使用三個引號的多行字符串)以及 switch 語句(或多或少借鑑了 Kotlin 的 when 語句)。我們可以通過 該地址查閱JDK 15 的發佈說明。

剛纔提到的這些,我都將其稱爲“ Java 的 Kotlin 化(Kotlinisation) ”。Kotlin 通過成爲 Java 有史以來最強的競爭對手,爲 Java 指明瞭發展的方向。在我看來,Kotlin 是我見過的唯一一種能夠戰勝 Java,併成爲行業領導者的語言。

ava 的“Kotlin”化

Java 的一些新特性主要在可讀性方面進行了增強,並改善了其主要的一個弱點,那就是繁瑣性。我們可以斷言, 它與某些 Kotlin 的特性具有相似性 

需要注意,這些特性大多數都還處於 特性預覽 階段,這意味着你安裝 JDK 14 或 JDK 15 時, 默認不會啓用這些特性

Java 特性預覽指的是一些在發佈版本中包含、但默認禁用的特性。在發行版中包含它們 僅僅是爲了收集社區開發人員的反饋 ,因此它們 更經常發生變更 ,這也是爲何不推薦在生產環境代碼中使用它們的原因。

要在編譯時啓用這些特性,我們需要執行如下代碼:

複製代碼
 
 
 
javac --enable-preview --release 14

如果你想要在運行時啓用它們的話,如下執行如下代碼:

複製代碼
 
 
 
java --enable-preview YourClass

當然,你也可以在 IDE 中啓用它們,但是不要在你的新項目中默認啓用預覽功能。

我們看一看這些變更,在未來的 Java 版本中,它們會對我們的編碼產生更大的影響。

Java record

Java record 是我們長期以來一直要求的一項特性,我相信你早就多次遇到這樣的場景了,那就是極不情願地實現 toString  hashCode  equals 方法以及每個字段的 getter(在這裏,我假設你已經不再實現 setter 了,還記得我們在“ Java 中新的併發模型”一文中所討論的不可變性嗎?)。

Kotlin 提供了 數據類(data class)來解決這個問題,Java 也通過發佈 record 類來解決了這個問題,同樣的問題,Scala 是通過 case 類來解決的。

這些類的主要目的是 在對象中保存不可變的數據 。讓我們通過一個示例來看看它在 Java 中能夠帶來多麼好的效果。我們實例化並對比 Employee 類,需要編寫這麼多的代碼:

複製代碼
 
 
 
package com.theboreddev.java14;
 
import java.util.Objects;
 
public class Employee {
 
private final String firstName;
 
private final String surname;
 
private final int age;
 
private final Address address;
 
private final double salary;
 
public Employee(String firstName, String surname, int age, Address address, double salary) {
 
this.firstName = firstName;
 
this.surname = surname;
 
this.age = age;
 
this.address = address;
 
this.salary = salary;
 
}
 
public String getFirstName() {
 
return firstName;
 
}
 
public String getSurname() {
 
return surname;
 
}
 
public int getAge() {
 
return age;
 
}
 
public Address getAddress() {
 
return address;
 
}
 
public double getSalary() {
 
return salary;
 
}
 
 
public boolean equals(Object o) {
 
if (this == o) return true;
 
if (o == null || getClass() != o.getClass()) return false;
 
Employee employee = (Employee) o;
 
return age == employee.age &&
 
Double.compare(employee.salary, salary) == 0 &&
 
Objects.equals(firstName, employee.firstName) &&
 
Objects.equals(surname, employee.surname) &&
 
Objects.equals(address, employee.address);
 
}
 
 
public int hashCode() {
 
return Objects.hash(firstName, surname, age, address, salary);
 
}
 
 
public String toString() {
 
return "Employee{" +
 
"firstName='" + firstName + '\'' +
 
", surname='" + surname + '\'' +
 
", age=" + age +
 
", address=" + address +
 
", salary=" + salary +
 
'}';
 
}
 
}

它所包含的 Address 對象如下所示:

複製代碼
 
 
 
package com.theboreddev.java14;
 
import java.util.Objects;
 
public class Address {
 
private final String firstLine;
 
private final String secondLine;
 
private final String postCode;
 
public Address(String firstLine, String secondLine, String postCode) {
 
this.firstLine = firstLine;
 
this.secondLine = secondLine;
 
this.postCode = postCode;
 
}
 
public String getFirstLine() {
 
return firstLine;
 
}
 
public String getSecondLine() {
 
return secondLine;
 
}
 
public String getPostCode() {
 
return postCode;
 
}
 
@Override
 
public boolean equals(Object o) {
 
if (this == o) return true;
 
if (o == null || getClass() != o.getClass()) return false;
 
Address address = (Address) o;
 
return Objects.equals(firstLine, address.firstLine) &&
 
Objects.equals(secondLine, address.secondLine) &&
 
Objects.equals(postCode, address.postCode);
 
}
 
@Override
 
public int hashCode() {
 
return Objects.hash(firstLine, secondLine, postCode);
 
}
 
@Override
 
public String toString() {
 
return "Address{" +
 
"firstLine='" + firstLine + '\'' +
 
", secondLine='" + secondLine + '\'' +
 
", postCode='" + postCode + '\'' +
 
'}';
 
}
 
}

爲了完成一件簡單的事情,我們寫了太多的代碼,對不對?

接下來,我們看一下使用新的 Java record 之後,代碼會是什麼樣子:

複製代碼
 
 
 
public record EmployeeRecord(String firstName, String surname, int age, AddressRecord address, double salary) {
 
}

再看一下 Address 類:

複製代碼
 
 
 
public record AddressRecord(String firstLine, String secondLine, String postCode) {
 
}

這和我們前面所編寫的一大堆代碼是同樣的效果,我們不得不承認:這非常棒!從要保存的代碼數量和簡潔性方面都是如此。

現在我們看看新的 switch 語句有什麼不同。

改善 switch 語句

新的 switch 語句解決了在 Java 中使用 switch 語句的一些固有問題。 我們一直以來都被教導應該避免使用 switch 語句,因爲它們很容易出錯並會導致代碼重複 。舉例來說,我們很容易遇到某個 case 條件覆蓋不到的場景。

新的 switch 語句解決了這個問題,因爲如果我們的 switch 語句沒有涵蓋我們傳遞給它的領域類型的所有範圍,它就無法編譯通過。

爲了闡述該例子,我們使用 Java 創建一個 DayOfTheWeek 枚舉:

複製代碼
 
 
 
public enum DayOfTheWeek {
 
MONDAY,
 
TUESDAY,
 
WEDNESDAY,
 
THURSDAY,
 
FRIDAY,
 
SATURDAY,
 
SUNDAY
 
}

我們需要 switch 語句告訴我們每週的某一天所對應的位置。看一下通過 Java 11 該怎麼實現:

複製代碼
 
 
 
final DayOfTheWeek dayOfTheWeek = DayOfTheWeek.THURSDAY;
 
int position = 0;
 
switch (dayOfTheWeek) {
 
case MONDAY:
 
position = 1;
 
break;
 
case TUESDAY:
 
position = 2;
 
break;
 
case WEDNESDAY:
 
position = 3;
 
break;
 
case THURSDAY:
 
position = 4;
 
break;
 
case FRIDAY:
 
position = 5;
 
break;
 
case SATURDAY:
 
position = 6;
 
break;
 
case SUNDAY:
 
position = 7;
 
break;
 
}
 
System.out.println("Day " + dayOfTheWeek + " is in position " + position + " of the week");

使用原來的 switch 語句時,我們必須要使用一個變量,而且如果我們遺漏了一週中的某一天,代碼也能編譯通過。這就是 switch 語句的問題之一,非常容易出錯。

Java 14 會怎樣改善這種情況呢?我們快速看一下:

複製代碼
 
 
 
final DayOfTheWeek dayOfTheWeek = DayOfTheWeek.THURSDAY;
 
int position = switch (dayOfTheWeek) {
 
case MONDAY -> 1;
 
case TUESDAY -> 2;
 
case WEDNESDAY -> 3;
 
case THURSDAY -> 4;
 
case FRIDAY -> 5;
 
case SATURDAY -> 6;
 
case SUNDAY -> 7;
 
};
 
System.out.println("Day " + dayOfTheWeek + " is in position " + position + " of the week");

我們可以看到, 新的 switch 語句可以用作表達式,而不僅僅是語句 

這樣帶來的結果就是更加簡潔,也更具有表述性,這就足以說服我們使用它了。但是,現在的 switch 還有一個重要改善,那就是如果在 switch 中沒有涵蓋所有 case 的話,它將無法編譯通過。它會顯示如下錯誤:

複製代碼
 
 
 
Error:(9, 24) java: the switch expression does not cover all possible input values

現在,我們不會在 switch 語句中遺漏 case 了,這是一項非常棒的特性。

這非常類似於 Kotlin 的 when 語句,你可以通過 該地址瞭解該語句的更多信息。

接下來,我們看一下文本塊。

文本塊

你有沒有遇到過將一個大的blob JSON 賦值給 Java 變量的場景?你是否也受夠了這種醜陋的代碼?Java 將會引入多行字符串特性,我們可以通過將它們 封裝在三重引號 中來定義它們。當這個功能被正式發佈後,定義多行長字符串會更加容易。

我們來看一下兩種模式的差異。假設我們想要將一個格式化後的 JSON 存儲到一個變量中,那麼醜陋的代碼如下所示:

複製代碼
 
 
 
final String text = "{\"widget\": {\n" +
 
" \"debug\": \"on\",\n" +
 
" \"window\": {\n" +
 
" \"title\": \"Sample Konfabulator Widget\",\n" +
 
" \"name\": \"main_window\",\n" +
 
" \"width\": 500,\n" +
 
" \"height\": 500\n" +
 
" },\n" +
 
" \"image\": { \n" +
 
" \"src\": \"Images/Sun.png\",\n" +
 
" \"name\": \"sun1\",\n" +
 
" \"hOffset\": 250,\n" +
 
" \"vOffset\": 250,\n" +
 
" \"alignment\": \"center\"\n" +
 
" },\n" +
 
" \"text\": {\n" +
 
" \"data\": \"Click Here\",\n" +
 
" \"size\": 36,\n" +
 
" \"style\": \"bold\",\n" +
 
" \"name\": \"text1\",\n" +
 
" \"hOffset\": 250,\n" +
 
" \"vOffset\": 100,\n" +
 
" \"alignment\": \"center\",\n" +
 
" \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n" +
 
" }\n" +
 
"}} ";
 
 

在新的多行字符串功能發佈之後,我們就可以更容易地編寫整潔的代碼了:

複製代碼
 
 
 
final String multiLineText = """
 
{"widget": {
 
"debug": "on",
 
"window": {
 
"title": "Sample Konfabulator Widget",
 
"name": "main_window",
 
"width": 500,
 
"height": 500
 
},
 
"image": {\s
 
"src": "Images/Sun.png",
 
"name": "sun1",
 
"hOffset": 250,
 
"vOffset": 250,
 
"alignment": "center"
 
},
 
"text": {
 
"data": "Click Here",
 
"size": 36,
 
"style": "bold",
 
"name": "text1",
 
"hOffset": 250,
 
"vOffset": 100,
 
"alignment": "center",
 
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
 
}
 
}}
 
""";

我覺得這樣好太多了。這也是 Kotlin 所支持的,可以在 這裏的類型定義中找到。

總之,我們能看到Java 從它的競爭對手之一,也就是Kotlin,那裏“繼承”了許多方案來解決自己的問題。我們不知道這次Oracle 在對抗Kotlin 的崛起方面是否及時做出了正確的反應,或許這有點太晚了。但我個人認爲Java 正在朝着正確的方向前進,儘管這些變化是由它的競爭對手以某種方式觸發的,而且可能來得有點遲了。

如前所述,如果這篇文章激發了你學習Kotlin 語言的興趣,我建議你閱讀“ Kotlin in Action ”,對於 Java 開發人員來說,這是一門很棒的 Kotlin 入門圖書。

結論

我認爲競爭是 Java 語言有史以來所遇到的最好的事情。如果不這樣,Java 就會作繭自縛。Java 的競爭對手也表明了不同的編程方式是可行的,它表明了前進的方向,並讓我們避免使用老式的、陳舊的編寫代碼方式。

我最近在 Java 中看到了一些變化,以及所有即將發佈的特性和改善,它們正在使 Java 變得比以往任何時候都更強大。它是一種適應當前時代的語言,一種希望發展並忘記傳統做事方式的語言: Java 的新未來!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章