抛弃 Java 改用 Kotlin 的六个月后,我后悔了!
點(diǎn)擊上方“朱小廝的博客”,選擇“設(shè)為星標(biāo)”
后臺(tái)回復(fù)"書",獲取
毫無(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)編寫一個(gè)項(xiàng)目,后來(lái) Kotlin 的各種怪異模式以及一些其他障礙越來(lái)越讓人厭煩,最終,他們決定重寫這個(gè)項(xiàng)目。
以下為譯文:
一直以來(lái),我對(duì)基于 JVM 的語(yǔ)言都非常情有獨(dú)鐘。我通常會(huì)用 Java 來(lái)編寫主程序,再用 Groovy 編寫測(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)重寫這個(gè)微服務(wù)項(xiàng)目。
那么 Kotlin 主要存在哪些弊端?下面來(lái)一一解釋。
名稱遮蔽
這是 Kotlin 最讓我震驚的地方。看看下面這個(gè)方法:
fun?inc(num?:?Int)?{val?num?=?2if?(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ù)。但是你可以用相同的名稱定義另一個(gè)變量并對(duì)其進(jìn)行初始化。
這樣一來(lái),這個(gè)方法作用域中就有兩個(gè)名為 num 的變量。當(dā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?scopeif?(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)映射類字段:
public?class?Shadow?{int?val;public?Shadow(int?val)?{this.val?=?val;} }在 Kotlin 中名稱遮蔽有些嚴(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ù)。
類型推斷
在Kotlin中,當(dāng)你聲明一個(gè)var或是val,你通常會(huì)讓編譯器從右邊的表達(dá)式類型中猜測(cè)變量類型。我們稱之為局部變量類型推斷,這對(duì)程序員來(lái)說(shuō)是一個(gè)很大的改進(jìn)。它允許我們?cè)诓挥绊戩o態(tài)類型檢查的情況下簡(jiǎn)化代碼。
例如,這個(gè)Kotlin代碼:
var?a?=?"10"Kotlin 編譯器會(huì)將其翻譯成:?
var?a?:?String?=?"10"Java 同樣具備這個(gè)特性,Java 10中的類型推斷示例如下:??
var?a?=?"10";實(shí)話實(shí)說(shuō),Kotlin 在這一點(diǎn)上確實(shí)更勝一籌。當(dāng)然,類型推斷還可應(yīng)用在多個(gè)場(chǎng)景。關(guān)于 Java 10中的局部變量類型推斷,點(diǎn)擊以下鏈接了解更多:
https://medium.com/@afinlay/java-10-sneak-peek-local-variable-type-inference-var-3022016e1a2b
Null 安全類型
Null 安全類型是 Kotlin 的殺手級(jí)功能。
這個(gè)想法很好,在 Kotlin 中,類型默認(rèn)不可為空。如果你需要添加一個(gè)可為空的類型,可以像下列代碼這樣:?
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 編寫的,我相信這種情況很常見(jiàn)。于是第三種類型產(chǎn)生了,它被稱為平臺(tái)類型。Kotlin 無(wú)法表示這種奇怪的類型,它只能從 Java 類型推斷出來(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)獲得這個(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?runtimeprintln?("f.len?:?"?+?f.length) }那你就需要用 Elvis 來(lái)解決這個(gè)問(wèn)題:
fun?doSth(text:?String)?{val?f:?String?=?Utils.format(text)??:?""??//?safe?with?Elvisprintln?("f.len?:?"?+?f.length) }第二種方法:你可以使用 String,能夠保證 Null 安全性。
fun?doSth(text:?String)?{val?f:?String??=?Utils.format(text)???//?safeprintln?("f.len?:?"?+?f.length)???????//?compilation?error,?fineprintln?("f.len?:?"?+?f?.length)??????//?null-safe?with???operator }第三種方法:讓 Kotlin 做局部變量類型推斷如何??
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 類型推斷為 String:
fun?doSth(text:?String)?{val?f?=?Utils.format(text)!!??????????//?throws?NPE?when?format()?returns?nullprintln?("f.len?:?"?+?f.length) }在我看來(lái),Kotlin 的所有這些類似 scala 的類型系統(tǒng)過(guò)于復(fù)雜。Java 互操作性似乎損害了 Kotlin 類型推斷這個(gè)重量級(jí)功能。
類名稱字面常量
使用類似 Log4j 或者 Gson 的 Java 庫(kù)時(shí),類文字很常見(jiàn)。
Java 使用 .class 后綴編寫類名:?
Gson?gson?=?new?GsonBuilder().registerTypeAdapter(LocalDate.class,?new?LocalDateAdapter()).create();Groovy 把類進(jìn)行了進(jìn)一步的簡(jiǎn)化。你可以忽略 .class,它是 Groovy 或者 Java 類并不重要。
def?gson?=?new?GsonBuilder().registerTypeAdapter(LocalDate,?new?LocalDateAdapter()).create()Kotlin 把 Kotlin 類和 Java 類進(jìn)行了區(qū)分,并為其提供了語(yǔ)法規(guī)范:
val?kotlinClass?:?KClass<LocalDate>?=?LocalDate::class val?javaClass?:?Class<LocalDate>?=?LocalDate::class.java因此在 Kotlin 中,你必須寫成如下形式:
val?gson?=?GsonBuilder().registerTypeAdapter(LocalDate::class.java,?LocalDateAdapter()).create()這看起來(lái)非常丑陋。
反向類型聲明
C 系列的編程語(yǔ)言有標(biāo)準(zhǔn)的聲明類型的方法。簡(jiǎn)而言之,首先指定一個(gè)類型,然后是該符合類型的東西,比如變量、字段、方法等等。
Java 中的表示方法是:
int?inc(int?i)?{return?i?+?1; }Kotlin 中則是:
fun?inc(i:?Int):?Int?{return?i?+?1 }這種方法有幾個(gè)原因令人討厭。
首先,你需要在名稱和類型之間加入這個(gè)多余的冒號(hào)。這個(gè)額外角色的目的是什么?為什么名稱與其類型要分離?我不知道。可悲的是,這讓你在 Kotlin 的工作變得更加困難。
第二個(gè)問(wèn)題,當(dāng)你讀取一個(gè)方法聲明時(shí),你首先看到的是名字和返回類型,然后才是參數(shù)。
在 Kotlin 中,方法的返回類型可能遠(yuǎn)在行尾,所以需要瀏覽很多代碼才能看到:?
private?fun?getMetricValue(kafkaTemplate?:?KafkaTemplate<String,?ByteArray>,?metricName?:?String)?:?Double?{... }或者,如果參數(shù)是逐行格式的,則需要搜索。那么我們需要多少時(shí)間才能找到此方法的返回類型呢?
@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<String,?ByteArray>?{val?bootstrapServer?=?if?(cloudMetadata.datacenter?==?"dc1")?{bootstrapServersDc1}... }第三個(gè)問(wèn)題是 IDE 中的自動(dòng)化支持不夠好。標(biāo)準(zhǔn)做法從類型名稱開(kāi)始,并且很容易找到類型。一旦選擇一個(gè)類型,IDE 會(huì)提供一些關(guān)于變量名的建議,這些變量名是從選定的類型派生的,因此你可以快速輸入這樣的變量:?
MongoExperimentsRepository?repositoryKotlin 盡管有 IntelliJ 這樣強(qiáng)大的 IDE,輸入變量仍然是很難的。如果你有多個(gè)存儲(chǔ)庫(kù),在列表中很難實(shí)現(xiàn)正確的自動(dòng)補(bǔ)全,這意味著你不得不手動(dòng)輸入完整的變量名稱。
repository?:?MongoExperimentsRepository伴生對(duì)象
一位 Java 程序員來(lái)到 Kotlin 面前。
“嗨,Kotlin。我是新來(lái)的,我可以使用靜態(tài)成員嗎?"他問(wèn)。
?“不行。我是面向?qū)ο蟮?#xff0c;靜態(tài)成員不是面向?qū)ο蟮摹!?Kotlin 回答。
?“好吧,但我需要 MyClass 的 logger,我該怎么辦?”?
“這個(gè)沒(méi)問(wèn)題,使用伴生對(duì)象即可。”
?“那是什么東西?” “這是局限到你的類的單獨(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)部類。事實(shí)上,這個(gè)類并不是匿名的,它的名字是 Companion,但你可以省略這個(gè)名字。看到了嗎?這很簡(jiǎn)單。"
我很欣賞對(duì)象聲明的概念——單例很有用。但從語(yǔ)言中刪除靜態(tài)成員是不切實(shí)際的。在 Java 中我們使用靜態(tài) Logger 很經(jīng)典,它只是一個(gè) Logger,所以我們不關(guān)心面向?qū)ο蟮募兌取K軌蚬ぷ?#xff0c;從來(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 邊界處理返回類型中的空值的非常流行的方式。
Kotlin 中沒(méi)有 Optional 的等價(jià)物,所以你大概應(yīng)該使用 Kotlin 的可空類型。讓我們來(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ù)類
數(shù)據(jù)類是 Kotlin 在實(shí)現(xiàn) Value Objects 時(shí)使用的方法,以減少 Java 中不可避免的樣板問(wèn)題。
例如,在 Kotlin 中,你只寫一個(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ù)類帶有嚴(yán)重的局限性。你無(wú)法擴(kuò)展數(shù)據(jù)類或者將其抽象化,所以你可能不會(huì)在核心模型中使用它們。
這個(gè)限制不是 Kotlin 的錯(cuò)。在 equals() 沒(méi)有違反 Liskov 原則的情況下,沒(méi)有辦法產(chǎn)生正確的基于價(jià)值的數(shù)據(jù)。
這也是為什么 Kotlin 不允許數(shù)據(jù)類繼承的原因。
開(kāi)放類
Kotlin 類默認(rèn)為 final。如果你想擴(kuò)展一個(gè)類,必須添加 open 修飾符。
繼承語(yǔ)法如下所示:?
open?class?Base class?Derived?:?Base()Kotlin 將 extends 關(guān)鍵字更改為: 運(yùn)算符,該運(yùn)算符用于將變量名稱與其類型分開(kāi)。那么再回到 C ++語(yǔ)法?對(duì)我來(lái)說(shuō)這很混亂。
這里有爭(zhēng)議的是,默認(rèn)情況下類是 final。也許 Java 程序員過(guò)度使用繼承,也許應(yīng)該在考慮擴(kuò)展類之前考慮三次。但我們生活在框架世界,Spring 使用 cglib、jassist 庫(kù)為你的 bean 生成動(dòng)態(tài)代理。Hibernate 擴(kuò)展你的實(shí)體以啟用延遲加載。
如果你使用 Spring,你有兩種選擇。你可以在所有 bean 類的前面添加 open,或者使用這個(gè)編譯器插件:?
buildscript?{dependencies?{classpath?group:?'org.jetbrains.kotlin',?name:?'kotlin-allopen',?version:?"$versions.kotlin"} }陡峭的學(xué)習(xí)曲線
如果你認(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年以上專業(yè)經(jīng)驗(yà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)簽的最高用戶列表中排名第三,并且是兩個(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)議的話題。后來(lái)我讀了這篇文章,果然證明了它是一種主觀的、不真實(shí)的、甚至有些居高臨下的偏見(jiàn)。
有些人已經(jīng)在原貼下進(jìn)行了合理的批評(píng),對(duì)此我也想表達(dá)一下自己的看法。
名稱遮蔽
“IDEA 團(tuán)隊(duì)”(或者 Kotlin 插件團(tuán)隊(duì))和“Kotlin 團(tuán)隊(duì)”肯定是同樣的人,我從不認(rèn)為內(nèi)部沖突會(huì)是個(gè)好事。語(yǔ)言提供這個(gè)功能給你,你需要的話就使用,如果討厭,調(diào)整檢查設(shè)置就是了。
類型推斷
Kotlin 的類型推斷無(wú)處不在,作者說(shuō)的 Java 10 同樣可以簡(jiǎn)直是在開(kāi)玩笑。
Kotlin 的方式超越了推斷局部變量類型或返回表達(dá)式體的函數(shù)類型。這里介紹的這兩個(gè)例子是那些剛剛看過(guò)關(guān)于 Kotlin 的第一次介紹性講話的人會(huì)提到的,而不是那些花了半年學(xué)習(xí)該語(yǔ)言的人。
例如,你怎么能不提 Kotlin 推斷泛型類型參數(shù)的方式?這不是 Kotlin 的一次性功能,它深深融入了整個(gè)語(yǔ)言。
編譯時(shí) Null 安全
這個(gè)批評(píng)是對(duì)的,當(dāng)你與 Java 代碼進(jìn)行互操作時(shí),Null 安全性確實(shí)被破壞了。該語(yǔ)言背后的團(tuán)隊(duì)曾多次聲明,他們最初試圖使 Java 可為空的每種類型,但他們發(fā)現(xiàn)它實(shí)際上讓代碼變得更糟糕。
Kotlin 不比 Java 更差,你只需要注意使用給定庫(kù)的方式,就像在 Java 中使用它一樣,因?yàn)樗](méi)有不去考慮 Null 安全。如果 Java 庫(kù)關(guān)心 Null 安全性,則它們會(huì)有許多支持注釋可供添加。
也許可以添加一個(gè)編譯器標(biāo)志,使每種 Java 類型都可以為空,但這對(duì) Kotlin 團(tuán)隊(duì)來(lái)說(shuō)不得不花費(fèi)大量額外資源。
類名稱字面常量
:: class 為你提供了一個(gè) KClass 實(shí)例,以便與 Kotlin 自己的反射 API 一起使用,而:: class.java為你提供了用于 Java 反射的常規(guī) Java 類實(shí)例。
反向類型聲明
為了清楚起見(jiàn),顛倒的順序是存在的,這樣你就可以以合理的方式省略顯式類型。冒號(hào)只是語(yǔ)法,這在現(xiàn)代語(yǔ)言中是相當(dāng)普遍的一種,比如 Scala、Swift 等。
我不知道作者在使用什么 IntelliJ,但我使用的變量名稱和類型都能夠自動(dòng)補(bǔ)全。對(duì)于參數(shù),IntelliJ 甚至?xí)o你提供相同類型的名稱和類型的建議,這實(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 <String>){?SpringApplication.run(AppRunner?::?class.java,*?args)}?或者這樣:
?fun?main(args:Array <String>){?runApplication?<AppRunner>(*?args)}集合字面量
你可以在注釋中使用數(shù)組文字。但是,除此之外,這些集合工廠的功能非常簡(jiǎn)潔,而且它們是另一種“內(nèi)置”到該語(yǔ)言的東西,而它們實(shí)際上只是庫(kù)函數(shù)。
你只是抱怨使用:進(jìn)行類型聲明。而且,為了獲得它不必是單獨(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ù)類
原文中說(shuō):
這個(gè)限制不是 Kotlin 的錯(cuò)。在 equals() 沒(méi)有違反 Liskov 原則的情況下,沒(méi)有辦法產(chǎn)生正確的基于價(jià)值的數(shù)據(jù)。
這就是為什么 Kotlin 不允許數(shù)據(jù)類繼承的原因。
我不知道你為什么提出這個(gè)問(wèn)題。如果你需要更復(fù)雜的類,你仍然可以創(chuàng)建它們并手動(dòng)維護(hù)它們的 equals、hashCode 等方法。數(shù)據(jù)類僅僅是一個(gè)簡(jiǎn)單用例的便捷方式,對(duì)于很多人來(lái)說(shuō)這很常見(jiàn)。
公開(kāi)類
作者再次鄙視了,對(duì)此我實(shí)在無(wú)話可說(shuō)。
陡峭的學(xué)習(xí)曲線
作者認(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é)編:郭芮
想知道更多?掃描下面的二維碼關(guān)注我后臺(tái)回復(fù)"技術(shù)",加入技術(shù)群【精彩推薦】超清晰的DNS入門指南
如何用ELK搭建TB級(jí)的日志系統(tǒng)
深度好文:Linux系統(tǒng)內(nèi)存知識(shí)
日志采集系統(tǒng)都用到哪些技術(shù)?
面試官:為什么HashMap的加載因子是0.75?
原創(chuàng)|OpenAPI標(biāo)準(zhǔn)規(guī)范
如此簡(jiǎn)單| ES最全詳細(xì)使用教程
ClickHouse到底是什么?為什么如此牛逼!
原來(lái)ElasticSearch還可以這么理解
點(diǎn)個(gè)贊+在看,少個(gè) bug?????
總結(jié)
以上是生活随笔為你收集整理的抛弃 Java 改用 Kotlin 的六个月后,我后悔了!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Hmily重启后月度报告
- 下一篇: 每日一题之 MySQL