拋棄 Java 改用 Kotlin 的六個(gè)月后,我后悔了!
點(diǎn)擊上方藍(lán)色“小哈學(xué)Java”,選擇“設(shè)為星標(biāo)”
回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!


毫無(wú)疑問(wèn),Kotlin 目前很受歡迎,業(yè)界甚至有人認(rèn)為其將取代 Java 的霸主地位。它提供了 Null 安全性,從這一點(diǎn)來(lái)說(shuō)它確實(shí)比 Java 更好。那么是不是這就意味著開(kāi)發(fā)者應(yīng)該毫不猶豫地?fù)肀?Kotlin,否則就落伍了?

等等,或許事情并非如此。
在開(kāi)始使用 Kotlin 編程之前,本文想要分享個(gè)故事給你。在這個(gè)故事中,作者最早使用 Kotlin 來(lái)編寫(xiě)一個(gè)項(xiàng)目,后來(lái) Kotlin 的各種怪異模式以及一些其他障礙越來(lái)越讓人厭煩,最終,他們決定重寫(xiě)這個(gè)項(xiàng)目。
以下為譯文:
一直以來(lái),我對(duì)基于 JVM 的語(yǔ)言都非常情有獨(dú)鐘。我通常會(huì)用 Java 來(lái)編寫(xiě)主程序,再用 Groovy 編寫(xiě)測(cè)試代碼,兩者配合使用得心應(yīng)手。
2017年夏天,團(tuán)隊(duì)發(fā)起了一個(gè)新的微服務(wù)項(xiàng)目,和往常一樣,我們需要對(duì)編程語(yǔ)言和技術(shù)進(jìn)行選型。部分團(tuán)隊(duì)成員是 Kotlin 的擁護(hù)者,再加上我們都想嘗試一下新的東西,于是我們決定用 Kotlin 來(lái)開(kāi)發(fā)這個(gè)項(xiàng)目。由于 Spock 測(cè)試框架不支持 Kotlin,因此我們決定堅(jiān)持使用 Groovy 來(lái)測(cè)試。
2018年春天,使用 Kotlin 開(kāi)發(fā)幾個(gè)月之后,我們總結(jié)了 Kotlin 的優(yōu)缺點(diǎn),最終結(jié)論表明 Kotlin 降低了我們的生產(chǎn)力。
于是我們使用 Java 來(lái)重寫(xiě)這個(gè)微服務(wù)項(xiàng)目。
那么 Kotlin 主要存在哪些弊端?下面來(lái)一一解釋。
名稱(chēng)遮蔽
這是 Kotlin 最讓我震驚的地方。看看下面這個(gè)方法:
fun?inc(num?:?Int)?{
????val?num?=?2
????if?(num?>?0)?{
????????val?num?=?3
????}
????println?("num:?"?+?num)
}
當(dāng)你調(diào)用 inc(1) 會(huì)輸出什么呢?在 Kotlin 中, 方法的參數(shù)無(wú)法修改,因此在本例中你不能改變 num。這個(gè)設(shè)計(jì)很好,因?yàn)槟悴粦?yīng)該改變方法的輸入?yún)?shù)。但是你可以用相同的名稱(chēng)定義另一個(gè)變量并對(duì)其進(jìn)行初始化。
這樣一來(lái),這個(gè)方法作用域中就有兩個(gè)名為 num 的變量。當(dāng)然,你一次只能訪(fǎng)問(wèn)其中一個(gè) num,但是 num 值會(huì)被改變。
在 if 語(yǔ)句中再添加另一個(gè) num,因?yàn)樽饔糜虻脑?num 并不會(huì)被修改。
于是,在 Kotlin 中,inc(1) 會(huì)輸出 2。同樣效果的 Java 代碼如下所示,不過(guò)無(wú)法通過(guò)編譯:?
void?inc(int?num)?{
????int?num?=?2;?//error:?variable?'num'?is?already?defined?in?the?scope
????if?(num?>?0)?{
????????int?num?=?3;?//error:?variable?'num'?is?already?defined?in?the?scope
????}
????System.out.println?("num:?"?+?num);
}名字遮蔽并不是 Kotlin 發(fā)明的,這在編程語(yǔ)言中很常見(jiàn)。在 Java 中我們習(xí)慣用方法參數(shù)來(lái)映射類(lèi)字段:
public?class?Shadow?{
????int?val;
????public?Shadow(int?val)?{
????????this.val?=?val;
????}
}在 Kotlin 中名稱(chēng)遮蔽有些嚴(yán)重,這是 Kotlin 團(tuán)隊(duì)的一個(gè)設(shè)計(jì)缺陷。
IDEA 團(tuán)隊(duì)試圖通過(guò)向每個(gè)遮蔽變量顯示警告信息來(lái)解決這個(gè)問(wèn)題。兩個(gè)團(tuán)隊(duì)在同一家公司工作,或許他們可以互相交流并就遮蔽問(wèn)題達(dá)成共識(shí)。我從個(gè)人角度贊成 IDEA 的做法因?yàn)槲蚁氩坏接心男?yīng)用場(chǎng)景需要遮蔽方法參數(shù)。
類(lèi)型推斷
在Kotlin中,當(dāng)你聲明一個(gè)var或是val,你通常會(huì)讓編譯器從右邊的表達(dá)式類(lèi)型中猜測(cè)變量類(lèi)型。我們稱(chēng)之為局部變量類(lèi)型推斷,這對(duì)程序員來(lái)說(shuō)是一個(gè)很大的改進(jìn)。它允許我們?cè)诓挥绊戩o態(tài)類(lèi)型檢查的情況下簡(jiǎn)化代碼。
例如,這個(gè)Kotlin代碼:
var?a?=?"10"Kotlin 編譯器會(huì)將其翻譯成:?
var?a?:?String?=?"10"Java 同樣具備這個(gè)特性,Java 10中的類(lèi)型推斷示例如下:??
var?a?=?"10";實(shí)話(huà)實(shí)說(shuō),Kotlin 在這一點(diǎn)上確實(shí)更勝一籌。當(dāng)然,類(lèi)型推斷還可應(yīng)用在多個(gè)場(chǎng)景。關(guān)于 Java 10中的局部變量類(lèi)型推斷,點(diǎn)擊以下鏈接了解更多:
https://medium.com/@afinlay/java-10-sneak-peek-local-variable-type-inference-var-3022016e1a2b
Null 安全類(lèi)型
Null 安全類(lèi)型是 Kotlin 的殺手級(jí)功能。
這個(gè)想法很好,在 Kotlin 中,類(lèi)型默認(rèn)不可為空。如果你需要添加一個(gè)可為空的類(lèi)型,可以像下列代碼這樣:?
val?a:?String??=?null??????//?ok
val?b:?String?=?null???????//?compilation?error假設(shè)你使用了可為空的變量但是并未進(jìn)行空值檢查,這在 Kotlin 將無(wú)法通過(guò)編譯,比如:
println?(a.length)??????????//?compilation?error
println?(a?.length)?????????//?fine,?prints?null
println?(a?.length??:?0)????//?fine,?prints?0那么是不是如果你同時(shí)擁有不可為空和可為空的變量,就可以避免 Java 中最常見(jiàn)的 NullPointerException 異常嗎?事實(shí)并沒(méi)有想象的簡(jiǎn)單。
當(dāng) Kotlin 代碼必須調(diào)用 Java 代碼時(shí),事情會(huì)變得很糟糕,比如庫(kù)是用 Java 編寫(xiě)的,我相信這種情況很常見(jiàn)。于是第三種類(lèi)型產(chǎn)生了,它被稱(chēng)為平臺(tái)類(lèi)型。Kotlin 無(wú)法表示這種奇怪的類(lèi)型,它只能從 Java 類(lèi)型推斷出來(lái)。它可能會(huì)誤導(dǎo)你,因?yàn)樗鼘?duì)空值很寬松,并且會(huì)禁用 Kotlin 的 NULL 安全機(jī)制。
看看下面這個(gè) Java 方法:
public?class?Utils?{
????static?String?format(String?text)?{
????????return?text.isEmpty()???null?:?text;
????}
}假如你想調(diào)用 format(String)。應(yīng)該使用哪種類(lèi)型來(lái)獲得這個(gè) Java 方法的結(jié)果呢?你有三個(gè)選擇。
第一種方法:你可以使用 String,代碼看起來(lái)很安全,但是會(huì)拋出 NullPointerException 異常。
fun?doSth(text:?String)?{
????val?f:?String?=?Utils.format(text)???????//?compiles?but?assignment?can?throw?NPE?at?runtime
????println?("f.len?:?"?+?f.length)
}那你就需要用 Elvis 來(lái)解決這個(gè)問(wèn)題:
fun?doSth(text:?String)?{
????val?f:?String?=?Utils.format(text)??:?""??//?safe?with?Elvis
????println?("f.len?:?"?+?f.length)
}第二種方法:你可以使用 String,能夠保證 Null 安全性。
fun?doSth(text:?String)?{
????val?f:?String??=?Utils.format(text)???//?safe
????println?("f.len?:?"?+?f.length)???????//?compilation?error,?fine
????println?("f.len?:?"?+?f?.length)??????//?null-safe?with???operator
}第三種方法:讓 Kotlin 做局部變量類(lèi)型推斷如何??
fun?doSth(text:?String)?{
????val?f?=?Utils.format(text)????????????//?f?type?inferred?as?String!
????println?("f.len?:?"?+?f.length)???????//?compiles?but?can?throw?NPE?at?runtime
}餿主意!這個(gè) Kotlin 代碼看起來(lái)很安全、可編譯,但是它容忍了空值,就像在 Java 中一樣。
除此之外,還有另外一個(gè)方法,就是強(qiáng)制將 f 類(lèi)型推斷為 String:
fun?doSth(text:?String)?{
????val?f?=?Utils.format(text)!!??????????//?throws?NPE?when?format()?returns?null
????println?("f.len?:?"?+?f.length)
}在我看來(lái),Kotlin 的所有這些類(lèi)似 scala 的類(lèi)型系統(tǒng)過(guò)于復(fù)雜。Java 互操作性似乎損害了 Kotlin 類(lèi)型推斷這個(gè)重量級(jí)功能。
類(lèi)名稱(chēng)字面常量
使用類(lèi)似 Log4j 或者 Gson 的 Java 庫(kù)時(shí),類(lèi)文字很常見(jiàn)。
Java 使用 .class 后綴編寫(xiě)類(lèi)名:?
Gson?gson?=?new?GsonBuilder().registerTypeAdapter(LocalDate.class,?new?LocalDateAdapter()).create();Groovy 把類(lèi)進(jìn)行了進(jìn)一步的簡(jiǎn)化。你可以忽略 .class,它是 Groovy 或者 Java 類(lèi)并不重要。
def?gson?=?new?GsonBuilder().registerTypeAdapter(LocalDate,?new?LocalDateAdapter()).create()Kotlin 把 Kotlin 類(lèi)和 Java 類(lèi)進(jìn)行了區(qū)分,并為其提供了語(yǔ)法規(guī)范:
val?kotlinClass?:?KClass?=?LocalDate::class
val?javaClass?:?Class?=?LocalDate::class.java 因此在 Kotlin 中,你必須寫(xiě)成如下形式:
val?gson?=?GsonBuilder().registerTypeAdapter(LocalDate::class.java,?LocalDateAdapter()).create()這看起來(lái)非常丑陋。
反向類(lèi)型聲明
C 系列的編程語(yǔ)言有標(biāo)準(zhǔn)的聲明類(lèi)型的方法。簡(jiǎn)而言之,首先指定一個(gè)類(lèi)型,然后是該符合類(lèi)型的東西,比如變量、字段、方法等等。
Java 中的表示方法是:
int?inc(int?i)?{
????return?i?+?1;
}Kotlin 中則是:
fun?inc(i:?Int):?Int?{
????return?i?+?1
}這種方法有幾個(gè)原因令人討厭。
首先,你需要在名稱(chēng)和類(lèi)型之間加入這個(gè)多余的冒號(hào)。這個(gè)額外角色的目的是什么?為什么名稱(chēng)與其類(lèi)型要分離?我不知道。可悲的是,這讓你在 Kotlin 的工作變得更加困難。
第二個(gè)問(wèn)題,當(dāng)你讀取一個(gè)方法聲明時(shí),你首先看到的是名字和返回類(lèi)型,然后才是參數(shù)。
在 Kotlin 中,方法的返回類(lèi)型可能遠(yuǎn)在行尾,所以需要瀏覽很多代碼才能看到:?
private?fun?getMetricValue(kafkaTemplate?:?KafkaTemplate<String,?ByteArray>,?metricName?:?String)?:?Double?{
????...
}或者,如果參數(shù)是逐行格式的,則需要搜索。那么我們需要多少時(shí)間才能找到此方法的返回類(lèi)型呢?
@Bean
fun?kafkaTemplate(
????????@Value("\${interactions.kafka.bootstrap-servers-dc1}")?bootstrapServersDc1:?String,
????????@Value("\${interactions.kafka.bootstrap-servers-dc2}")?bootstrapServersDc2:?String,
????????cloudMetadata:?CloudMetadata,
????????@Value("\${interactions.kafka.batch-size}")?batchSize:?Int,
????????@Value("\${interactions.kafka.linger-ms}")?lingerMs:?Int,
????????metricRegistry?:?MetricRegistry
):?KafkaTemplate?{
????val?bootstrapServer?=?if?(cloudMetadata.datacenter?==?"dc1")?{
????????bootstrapServersDc1
????}
????...
}
第三個(gè)問(wèn)題是 IDE 中的自動(dòng)化支持不夠好。標(biāo)準(zhǔn)做法從類(lèi)型名稱(chēng)開(kāi)始,并且很容易找到類(lèi)型。一旦選擇一個(gè)類(lèi)型,IDE 會(huì)提供一些關(guān)于變量名的建議,這些變量名是從選定的類(lèi)型派生的,因此你可以快速輸入這樣的變量:?
MongoExperimentsRepository?repositoryKotlin 盡管有 IntelliJ 這樣強(qiáng)大的 IDE,輸入變量仍然是很難的。如果你有多個(gè)存儲(chǔ)庫(kù),在列表中很難實(shí)現(xiàn)正確的自動(dòng)補(bǔ)全,這意味著你不得不手動(dòng)輸入完整的變量名稱(chēng)。
repository?:?MongoExperimentsRepository伴生對(duì)象
一位 Java 程序員來(lái)到 Kotlin 面前。
“嗨,Kotlin。我是新來(lái)的,我可以使用靜態(tài)成員嗎?"他問(wèn)。
?“不行。我是面向?qū)ο蟮模o態(tài)成員不是面向?qū)ο蟮摹!?Kotlin 回答。
?“好吧,但我需要 MyClass 的 logger,我該怎么辦?”?
“這個(gè)沒(méi)問(wèn)題,使用伴生對(duì)象即可。”
?“那是什么東西?” “這是局限到你的類(lèi)的單獨(dú)對(duì)象。把你的 logger 放在伴生對(duì)象中。”Kotlin解釋說(shuō)。
?“我懂了。這樣對(duì)嗎?”
class?MyClass?{
????companion?object?{
????????val?logger?=?LoggerFactory.getLogger(MyClass::class.java)
????}
}“正確!”
?“很詳細(xì)的語(yǔ)法,”程序員看起來(lái)很疑惑,“但是沒(méi)關(guān)系,現(xiàn)在我可以像 MyClass.logger 這樣調(diào)用我的 logger,就像 Java 中的一個(gè)靜態(tài)成員?”?
“嗯......是的,但它不是靜態(tài)成員!這里只有對(duì)象。把它看作是已經(jīng)實(shí)例化為單例的匿名內(nèi)部類(lèi)。事實(shí)上,這個(gè)類(lèi)并不是匿名的,它的名字是 Companion,但你可以省略這個(gè)名字。看到了嗎?這很簡(jiǎn)單。"
我很欣賞對(duì)象聲明的概念——單例很有用。但從語(yǔ)言中刪除靜態(tài)成員是不切實(shí)際的。在 Java 中我們使用靜態(tài) Logger 很經(jīng)典,它只是一個(gè) Logger,所以我們不關(guān)心面向?qū)ο蟮募兌取K軌蚬ぷ鳎瑥膩?lái)沒(méi)有任何壞處。
因?yàn)橛袝r(shí)候你必須使用靜態(tài)。舊版本 public static void main() 仍然是啟動(dòng) Java 應(yīng)用程序的唯一方式。
class?AppRunner?{
????companion?object?{
????????@JvmStatic?fun?main(args:?Array<String>)?{
????????????SpringApplication.run(AppRunner::class.java,?*args)
????????}
????}
}集合字面量
在Java中,初始化列表非常繁瑣:
import?java.util.Arrays;
...
List<String>?strings?=?Arrays.asList("Saab",?"Volvo");初始化地圖非常冗長(zhǎng),很多人使用 Guava:
import?com.google.common.collect.ImmutableMap;
...
Map<String,?String>?string?=?ImmutableMap.of("firstName",?"John",?"lastName",?"Doe");在 Java 中,我們?nèi)匀辉诘却碌恼Z(yǔ)法來(lái)表達(dá)集合和映射。語(yǔ)法在許多語(yǔ)言中非常自然和方便。
JavaScript:
const?list?=?['Saab',?'Volvo']
const?map?=?{'firstName':?'John',?'lastName'?:?'Doe'}Python:
list?=?['Saab',?'Volvo']
map?=?{'firstName':?'John',?'lastName':?'Doe'}Groovy:
def?list?=?['Saab',?'Volvo']
def?map?=?['firstName':?'John',?'lastName':?'Doe']簡(jiǎn)單來(lái)說(shuō),集合字面量的整齊語(yǔ)法就是你對(duì)現(xiàn)代編程語(yǔ)言的期望,特別是如果它是從頭開(kāi)始創(chuàng)建的。Kotlin 提供了一系列內(nèi)置函數(shù),比如 listOf()、mutableListOf()、mapOf()、hashMapOf() 等等。
Kotlin:?
val?list?=?listOf("Saab",?"Volvo")
val?map?=?mapOf("firstName"?to?"John",?"lastName"?to?"Doe")在地圖中,鍵和值與 to 運(yùn)算符配對(duì),這很好。但為什么一直沒(méi)有得到廣泛使用呢?令人失望。
Maybe
函數(shù)式語(yǔ)言(比如 Haskell)沒(méi)有空值。相反,他們提供 Maybe monad(如果你不熟悉monad,請(qǐng)閱讀 Tomasz Nurkiewicz 的這篇文章:http://www.nurkiewicz.com/2016/06/functor-and-monad-examples-in-plain-java.html)。
Maybe 很久以前就被 Scala 以 Option 引入到 JVM 世界,然后在 Java 8 中被采用為 Optional。如今,Optional 是在 API 邊界處理返回類(lèi)型中的空值的非常流行的方式。
Kotlin 中沒(méi)有 Optional 的等價(jià)物,所以你大概應(yīng)該使用 Kotlin 的可空類(lèi)型。讓我們來(lái)調(diào)查一下這個(gè)問(wèn)題。
通常情況下,當(dāng)你有一個(gè) Optional 的時(shí)候,你想要應(yīng)用一系列無(wú)效的轉(zhuǎn)換。
例如,在 Java 中:?
public?int?parseAndInc(String?number)?{
????return?Optional.ofNullable(number)
???????????????????.map(Integer::parseInt)
???????????????????.map(it?->?it?+?1)
???????????????????.orElse(0);
}在 Kotlin 中,為了映射你可以使用 let 函數(shù):
fun?parseAndInc(number:?String?):?Int?{
????return?number.let?{?Integer.parseInt(it)?}
?????????????????.let?{?it?->?it?+?1?}??:?0
}上面的代碼是錯(cuò)誤的,parseInt() 會(huì)拋出 NPE 。map() 僅在有值時(shí)執(zhí)行。否則,Null 就會(huì)跳過(guò),這就是為什么 map() 如此方便。不幸的是,Kotlin 的 let 不會(huì)那樣工作。它從左側(cè)的所有內(nèi)容中調(diào)用,包括空值。
為了保證這個(gè)代碼 Null 安全,你必須在每個(gè)代碼之前添加 let:?
fun?parseAndInc(number:?String?):?Int?{
????return?number?.let?{?Integer.parseInt(it)?}
??????????????????.let?{?it?->?it?+?1?}??:?0
}現(xiàn)在,比較 Java 和 Kotlin 版本的可讀性。你更傾向哪個(gè)?
數(shù)據(jù)類(lèi)
數(shù)據(jù)類(lèi)是 Kotlin 在實(shí)現(xiàn) Value Objects 時(shí)使用的方法,以減少 Java 中不可避免的樣板問(wèn)題。
例如,在 Kotlin 中,你只寫(xiě)一個(gè) Value Object :
data?class?User(val?name:?String,?val?age:?Int)Kotlin 對(duì) equals()、hashCode()、toString() 以及 copy() 有很好的實(shí)現(xiàn)。在實(shí)現(xiàn)簡(jiǎn)單的DTO 時(shí)它非常有用。但請(qǐng)記住,數(shù)據(jù)類(lèi)帶有嚴(yán)重的局限性。你無(wú)法擴(kuò)展數(shù)據(jù)類(lèi)或者將其抽象化,所以你可能不會(huì)在核心模型中使用它們。
這個(gè)限制不是 Kotlin 的錯(cuò)。在 equals() 沒(méi)有違反 Liskov 原則的情況下,沒(méi)有辦法產(chǎn)生正確的基于價(jià)值的數(shù)據(jù)。
這也是為什么 Kotlin 不允許數(shù)據(jù)類(lèi)繼承的原因。
開(kāi)放類(lèi)
Kotlin 類(lèi)默認(rèn)為 final。如果你想擴(kuò)展一個(gè)類(lèi),必須添加 open 修飾符。
繼承語(yǔ)法如下所示:?
open?class?Base
class?Derived?:?Base()Kotlin 將 extends 關(guān)鍵字更改為: 運(yùn)算符,該運(yùn)算符用于將變量名稱(chēng)與其類(lèi)型分開(kāi)。那么再回到 C ++語(yǔ)法?對(duì)我來(lái)說(shuō)這很混亂。
這里有爭(zhēng)議的是,默認(rèn)情況下類(lèi)是 final。也許 Java 程序員過(guò)度使用繼承,也許應(yīng)該在考慮擴(kuò)展類(lèi)之前考慮三次。但我們生活在框架世界,Spring 使用 cglib、jassist 庫(kù)為你的 bean 生成動(dòng)態(tài)代理。Hibernate 擴(kuò)展你的實(shí)體以啟用延遲加載。
如果你使用 Spring,你有兩種選擇。你可以在所有 bean 類(lèi)的前面添加 open,或者使用這個(gè)編譯器插件:?
buildscript?{
????dependencies?{
????????classpath?group:?'org.jetbrains.kotlin',?name:?'kotlin-allopen',?version:?"$versions.kotlin"
????}
}陡峭的學(xué)習(xí)曲線(xiàn)
如果你認(rèn)為自己有 Java 基礎(chǔ)就可以快速學(xué)習(xí) Kotlin,那你就錯(cuò)了。Kotlin 會(huì)讓你陷入深淵,事實(shí)上,Kotlin 的語(yǔ)法更接近 Scala。這是一項(xiàng)賭注,你將不得不忘記 Java 并切換到完全不同的語(yǔ)言。
相反,學(xué)習(xí) Groovy 是一個(gè)愉快的過(guò)程。Java 代碼是正確的 Groovy 代碼,因此你可以通過(guò)將文件擴(kuò)展名從 .java 更改為 .groovy。
最后的想法
學(xué)習(xí)新技術(shù)就像一項(xiàng)投資。我們投入時(shí)間,新技術(shù)讓我們得到回報(bào)。但我并不是說(shuō) Kotlin 是一種糟糕的語(yǔ)言,只是在我們的案例中,成本遠(yuǎn)超收益。
以上內(nèi)容編譯自 From Java to Kotlin and Back Again,作者 Kotlin ketckup。
他是一名具有15年以上專(zhuān)業(yè)經(jīng)驗(yàn)的軟件工程師,專(zhuān)注于JVM 。在 Allegro,他是一名開(kāi)發(fā)團(tuán)隊(duì)負(fù)責(zé)人,JaVers 項(xiàng)目負(fù)責(zé)人,Spock 倡導(dǎo)者。此外,他還是 allegro.tech/blog 的主編。
本文一出就引發(fā)了業(yè)內(nèi)的廣泛爭(zhēng)議,Kotlin 語(yǔ)言擁護(hù)者 Márton Braun 就表示了強(qiáng)烈的反對(duì)。
Márton Braun 十分喜歡 Kotlin 編程,目前他在 StackOverflow 上 Kotlin 標(biāo)簽的最高用戶(hù)列表中排名第三,并且是兩個(gè)開(kāi)源 Kotlin 庫(kù)的創(chuàng)建者,最著名的是 MaterialDrawerKt。此外他還是 Autosoft 的 Android 開(kāi)發(fā)人員,目前正在布達(dá)佩斯技術(shù)經(jīng)濟(jì)大學(xué)攻讀計(jì)算機(jī)工程碩士學(xué)位。
以下就是他針對(duì)上文的反駁:
當(dāng)我第一次看到這篇文章時(shí),我就想把它轉(zhuǎn)發(fā)出來(lái)看看大家會(huì)怎么想,我肯定它會(huì)是一個(gè)有爭(zhēng)議的話(huà)題。后來(lái)我讀了這篇文章,果然證明了它是一種主觀的、不真實(shí)的、甚至有些居高臨下的偏見(jiàn)。
有些人已經(jīng)在原貼下進(jìn)行了合理的批評(píng),對(duì)此我也想表達(dá)一下自己的看法。
名稱(chēng)遮蔽
“IDEA 團(tuán)隊(duì)”(或者 Kotlin 插件團(tuán)隊(duì))和“Kotlin 團(tuán)隊(duì)”肯定是同樣的人,我從不認(rèn)為內(nèi)部沖突會(huì)是個(gè)好事。語(yǔ)言提供這個(gè)功能給你,你需要的話(huà)就使用,如果討厭,調(diào)整檢查設(shè)置就是了。
類(lèi)型推斷
Kotlin 的類(lèi)型推斷無(wú)處不在,作者說(shuō)的 Java 10 同樣可以簡(jiǎn)直是在開(kāi)玩笑。
Kotlin 的方式超越了推斷局部變量類(lèi)型或返回表達(dá)式體的函數(shù)類(lèi)型。這里介紹的這兩個(gè)例子是那些剛剛看過(guò)關(guān)于 Kotlin 的第一次介紹性講話(huà)的人會(huì)提到的,而不是那些花了半年學(xué)習(xí)該語(yǔ)言的人。
例如,你怎么能不提 Kotlin 推斷泛型類(lèi)型參數(shù)的方式?這不是 Kotlin 的一次性功能,它深深融入了整個(gè)語(yǔ)言。
編譯時(shí) Null 安全
這個(gè)批評(píng)是對(duì)的,當(dāng)你與 Java 代碼進(jìn)行互操作時(shí),Null 安全性確實(shí)被破壞了。該語(yǔ)言背后的團(tuán)隊(duì)曾多次聲明,他們最初試圖使 Java 可為空的每種類(lèi)型,但他們發(fā)現(xiàn)它實(shí)際上讓代碼變得更糟糕。
Kotlin 不比 Java 更差,你只需要注意使用給定庫(kù)的方式,就像在 Java 中使用它一樣,因?yàn)樗](méi)有不去考慮 Null 安全。如果 Java 庫(kù)關(guān)心 Null 安全性,則它們會(huì)有許多支持注釋可供添加。
也許可以添加一個(gè)編譯器標(biāo)志,使每種 Java 類(lèi)型都可以為空,但這對(duì) Kotlin 團(tuán)隊(duì)來(lái)說(shuō)不得不花費(fèi)大量額外資源。
類(lèi)名稱(chēng)字面常量
:: class 為你提供了一個(gè) KClass 實(shí)例,以便與 Kotlin 自己的反射 API 一起使用,而:: class.java為你提供了用于 Java 反射的常規(guī) Java 類(lèi)實(shí)例。
反向類(lèi)型聲明
為了清楚起見(jiàn),顛倒的順序是存在的,這樣你就可以以合理的方式省略顯式類(lèi)型。冒號(hào)只是語(yǔ)法,這在現(xiàn)代語(yǔ)言中是相當(dāng)普遍的一種,比如 Scala、Swift 等。
我不知道作者在使用什么 IntelliJ,但我使用的變量名稱(chēng)和類(lèi)型都能夠自動(dòng)補(bǔ)全。對(duì)于參數(shù),IntelliJ 甚至?xí)o你提供相同類(lèi)型的名稱(chēng)和類(lèi)型的建議,這實(shí)際上比 Java 更好。
伴生對(duì)象
原文中說(shuō):
有時(shí)候你必須使用靜態(tài)。舊版本 public static void main() 仍然是啟動(dòng) Java 應(yīng)用程序的唯一方式。
class?AppRunner?{
????companion?object?{
????????@JvmStatic?fun?main(args:?Array<String>)?{
????????????SpringApplication.run(AppRunner::class.java,?*args)
????????}
????}
}實(shí)際上,這不是啟動(dòng) Java 應(yīng)用程序的唯一方式。你可以這樣做:
?fun?main(args:Array ) {?SpringApplication.run(AppRunner?::?class.java,*?args)}?
或者這樣:
?fun?main(args:Array ) {?runApplication?(*?args)}
集合字面量
你可以在注釋中使用數(shù)組文字。但是,除此之外,這些集合工廠的功能非常簡(jiǎn)潔,而且它們是另一種“內(nèi)置”到該語(yǔ)言的東西,而它們實(shí)際上只是庫(kù)函數(shù)。
你只是抱怨使用:進(jìn)行類(lèi)型聲明。而且,為了獲得它不必是單獨(dú)的語(yǔ)言結(jié)構(gòu)的好處,它只是一個(gè)任何人都可以實(shí)現(xiàn)的功能。
Maybe
如果你喜歡 Optional ,你可以使用它。Kotlin 在 JVM 上運(yùn)行。
對(duì)于代碼確實(shí)這有些難看。但是你不應(yīng)該在 Kotlin 代碼中使用 parseInt,而應(yīng)該這樣做(我不知道你使用該語(yǔ)言的 6 個(gè)月中為何錯(cuò)過(guò)這個(gè))。你為什么要明確地命名一個(gè) Lambda 參數(shù)呢?
數(shù)據(jù)類(lèi)
原文中說(shuō):
這個(gè)限制不是 Kotlin 的錯(cuò)。在 equals() 沒(méi)有違反 Liskov 原則的情況下,沒(méi)有辦法產(chǎn)生正確的基于價(jià)值的數(shù)據(jù)。
這就是為什么 Kotlin 不允許數(shù)據(jù)類(lèi)繼承的原因。
我不知道你為什么提出這個(gè)問(wèn)題。如果你需要更復(fù)雜的類(lèi),你仍然可以創(chuàng)建它們并手動(dòng)維護(hù)它們的 equals、hashCode 等方法。數(shù)據(jù)類(lèi)僅僅是一個(gè)簡(jiǎn)單用例的便捷方式,對(duì)于很多人來(lái)說(shuō)這很常見(jiàn)。
公開(kāi)類(lèi)
作者再次鄙視了,對(duì)此我實(shí)在無(wú)話(huà)可說(shuō)。
陡峭的學(xué)習(xí)曲線(xiàn)
作者認(rèn)為學(xué)習(xí) Kotlin 很難, 但是我個(gè)人并不這么認(rèn)為。
最后的想法
從作者列舉的例子中,我感覺(jué)他只是了解語(yǔ)言的表面。
很難想象他對(duì)此有投入很多時(shí)間。
原文:
https://allegro.tech/2018/05/From-Java-to-Kotlin-and-Back-Again.html
https://zsmb.co/on-from-java-to-kotlin-and-back-again/
譯者:安翔,責(zé)編:郭芮
END
有熱門(mén)推薦?
1.?牛x!一個(gè)比傳統(tǒng)數(shù)據(jù)庫(kù)快 100-1000 倍的數(shù)據(jù)庫(kù)!
2.?必須了解的mysql三大日志-binlog、redo log和undo log
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話(huà),在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)

