close

作者 | Nicolas Fränkel
譯者 | 彎月
出品 | CSDN(ID:CSDNnews)

我使用Java已近二十年了。幾年前,我開始學習Kotlin。

雖然Kotlin也會編譯成JVM字節碼,但有時候我還是要寫Java。每當這時,我就會想,為什麼Java代碼不能像Kotlin那樣漂亮。Java缺少一些關鍵特性,因此代碼的可讀性、表達性和可維護性都差強人意。

這篇文章並不是要攻擊Java,只是列出了一些我希望Java擁有的功能。

不可變引用

Java有不可變引用:

類的屬性

方法的參數

局部變量

class Foo { final Object bar = new Object(); ① void baz(final Object qux) { ②final var corge = new Object(); ③}}

① 不能給bar重新賦值

② 不能給qux重新賦值

③ 不能給corge重新賦值

不可變引用非常有利於避免尷尬的bug。有意思的是,final關鍵字並沒有被廣泛使用,即使是廣為人知的項目也並沒有使用太多final。例如,Spring的GenericBean使用了不可變屬性,但沒有使用不可變方法參數,也沒有使用不可變局部變量;slf4j的DefaultLoggingEventBuilder沒有使用上述任何一種。

儘管Java允許定義不可變引用,但並沒有強制要求。默認情況下,引用是可變的。大部分Java代碼都沒有採用不可變引用。

而Kotlin並沒有給你選擇:每個屬性和局部變量都要定義為val或var。而且,方法參數不能重新賦值。

Java的var關鍵字則有很大的不同。首先,它只能用於局部變量。更重要的是,Java並沒有相應的不可變關鍵字val。你依然需要使用final關鍵字,而很少有人這麼做。

Null安全性

在Java中,沒有辦法知道某個變量是否為null。為了明確這一點,Java 8引入了Optional類型。從Java 8以後,返回一個Optional意味着底層的值可能為null,而返回其他類型意味着不可能為null。

但是,Optional的開發者只用null作為返回值。而方法參數和返回值在Null安全性方面並沒有得到語法層面上的支持。為了解決這個問題,許多庫提供了編譯時注釋:

顯然,一些庫只能用於特定的IDE。更糟糕的是,這些庫之間很難相互兼容。所以很多人都在Stack Overflow上問,這麼多的庫應該使用哪個。

最後,開發者必須主動使用支持Null安全性的庫。相反,Kotlin要求每個類型都必須是允許null或不允許null。

val nonNullable: String = computeNonNullableString()val nullable: String? = computeNullableString()

擴展函數

在Java中,擴展類的方法是編寫子類:

class Foo {}class Bar extends Bar {}

子類有兩個主要問題。首先,標記了final的類不允許繼承。許多廣泛應用的JDK類都是final的,比如String。其次,如果一個不屬於方法返回了某個類型,那麼就只能返回那個類型,不論其行為是否符合你的要求。

為了解決這個問題,Java開發者發明了工具類的概念,例如類型XYZ的工具類通常寫作XYZUtils。工具類就是一堆static方法的集合,並且構造函數是private的,因此無法創建示例。這就相當於一個命名空間,因為Java不允許在類外創建方法。

這樣,如果一個類型不包含某個方法,那麼工具類可以提供該方法,接受類型作為一個參數,並執行指定的行為。

class StringUtils { ① private StringUtils() {} ② static String capitalize(String string) { ③return string.substring(0, 1).toUpperCase()+ string.substring(1); ④}}String string = randomString(); ⑤String capitalizedString = StringUtils.capitalize(string); ⑥

① 工具類② 防止工具類實例化③ static方法④ 一個簡單的大寫函數,沒有考慮邊界情況⑤ String類型沒有提供大寫功能⑥ 使用工具類來實現該功能

而Kotlin提供了擴展函數功能來解決這個問題。

Kotlin提供了一種方法,可以擴展類或接口,而無需從類進行集成,也無需使用諸如修飾器等設計模式。只需通過一種叫做「擴展」的特殊定義來實現。

例如,你可以給一個來自第三方庫的類或接口編寫新的函數,即使你無法修改該庫。這種函數可以正常調用,就像它本來就屬於該類一樣。這種機制叫做「函數擴展」。

要定義函數擴展,只需在其名稱前加上一個接收者類型,指示被擴展的類。

有了函數擴展,上述代碼就可以寫成:

fun String.capitalize2(): String { ①②return substring(0, 1).uppercase() + substring(1);}val string = randomString()val capitalizedString = string.capitalize2() ③

① 孤立的函數,不需要類封裝。

② Kotlin的stdlib已經有了capitalize()函數。

③ 就像調用String自帶的函數一樣調用擴展函數。

注意擴展函數會被「靜態地」解析。擴展函數並不會給已有類型添加新的行為,只是假裝而已。它們生成的字節碼非常類似於Java的靜態方法。但是其語法要簡潔得多,而且支持函數鏈式調用,這在Java中時無法做到的。

真實泛型

Java版本5加入了泛型支持。但是,語言設計師太執着於向下兼容性,Java 5的字節碼必須能與Java 5之前的字節碼完全兼容。這就是為什麼生成的字節碼中不包含泛型的原因。這種方式稱為「泛型擦除」。與之相對的叫做「真實泛型」(reified generics),即泛型會出現在字節碼中。

僅在編譯期間採用泛型,會導致一系列問題。例如,下面的方法簽名會生成完全相同的字節碼,因此這段代碼是不正確的:

class Bag {int compute(List<Foo> persons) {}int compute(List<Bar> persons) {}}

另一個問題是如何從值的容器中獲取有類型的值。下面是Spring中的一個例子:

org/springframework/beans/factory/BeanFactory.javapublic interface BeanFactory {<T> T getBean(Class<T> requiredType);}

開發者添加了一個 Class<T> ,以便在方法體中獲知類型。如果Java有真實泛型,只需像下面這樣處理即可:

public interface BeanFactory {<T> T getBean();}

想象一下,如果Kotlin有真實泛型,我們可以改變上述設計:

interface BeanFactory {fun <T> getBean(): T}

函數調用可以改成:

val factory = getBeanFactory()val anyBean = factory.getBean<Any>() ①

① 真實泛型!

Kotlin依然需要遵守JVM規範,生成與Java編譯器的字節碼兼容的字節碼。但它可以通過「內聯」的方式實現,即編譯器用函數體替換內聯函數調用。

下面是Kotlin代碼:

org/springframework/beans/factory/BeanFactoryExtensions.ktinline fun <reified T : Any> BeanFactory.getBean(): T = getBean(T::class.java)

總結

本文介紹了四個我希望Java也有的Kotlin功能:不可變引用、null安全性、擴展函數,以及真實泛型。Kotlin還有許多其他很好的功能,但這四個功能就足以提升Java。

例如,有了擴展函數和真是繁星,再加上一些語法糖,就可以很輕鬆地編寫DSL,就像Kotlin Routes和Beans DSL一樣:

beans {bean {router {GET("/hello") { ServerResponse.ok().body("Hello world!") }}}}

別誤會:我知道Java作為一種語言,發展時需要考慮很多因素,而Kotlin的包袱更輕。但是,有競爭是好事,兩者可以互相學習。

同時,我只在必要時才會編寫Java,因為Kotlin已成為了我的JVM首選。

原文地址:https://blog.frankel.ch/miss-in-java-kotlin-developer/

— 推薦閱讀 —
☞M2 芯片解析:似乎是一個增強版的 A15?
☞前 AMD 芯片架構師吐槽,取消 K12 處理器項目是因為 AMD 慫了!
☞學習通被曝信息泄露:超 1.7 億條隱私數據售賣 1.2 萬元,甚至包含密碼!

—點這裡↓↓↓記得關註標星哦~—


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()