自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從Java到 Kotlin,再?gòu)腒otlin回歸Java

開(kāi)發(fā) 后端
由于此博客文章引起高度關(guān)注和爭(zhēng)議,我們認(rèn)為值得在Allegro上增加一些關(guān)于我們?nèi)绾喂ぷ骱妥龀鰶Q策的背景。Allegro擁有超過(guò)50個(gè)開(kāi)發(fā)團(tuán)隊(duì)可以自由選擇被我們PaaS所支持的技術(shù)。我們主要使用Java、Kotlin、Python和Golang進(jìn)行編碼。本文中提出的觀點(diǎn)來(lái)自作者的經(jīng)驗(yàn)。

由于此博客文章引起高度關(guān)注和爭(zhēng)議,我們認(rèn)為值得在Allegro上增加一些關(guān)于我們?nèi)绾喂ぷ骱妥龀鰶Q策的背景。Allegro擁有超過(guò)50個(gè)開(kāi)發(fā)團(tuán)隊(duì)可以自由選擇被我們PaaS所支持的技術(shù)。我們主要使用Java、Kotlin、Python和Golang進(jìn)行編碼。本文中提出的觀點(diǎn)來(lái)自作者的經(jīng)驗(yàn)。

[[231717]]

Kotlin很流行,Kotlin很時(shí)髦。Kotlin為你提供了編譯時(shí)null-safety和更少的boilerplate。當(dāng)然,它比Java更好。你應(yīng)該切換到Kotlin或作為碼農(nóng)遺老直到死亡。等等,或者你不應(yīng)該如此?在開(kāi)始使用Kotlin編寫(xiě)之前,請(qǐng)閱讀一個(gè)項(xiàng)目的故事。關(guān)于奇技和障礙的故事變得如此令人討厭,因此我們決定重寫(xiě)之。

我們嘗試過(guò)Kotlin,但現(xiàn)在我們正在用Java10重寫(xiě)

我有我最喜歡的JVM語(yǔ)言集。Java的/main和Groovy的/test對(duì)我來(lái)說(shuō)是組好的組合。2017年夏季,我的團(tuán)隊(duì)開(kāi)始了一個(gè)新的微服務(wù)項(xiàng)目,我們就像往常一樣談?wù)摿苏Z(yǔ)言和技術(shù)。在Allegro有幾個(gè)支持Kotlin的團(tuán)隊(duì),而且我們也想嘗試新的東西,所以我們決定試試Kotlin。由于Kotlin中沒(méi)有 Spock 的替代品,我們決定繼續(xù)在/test中使用Groovy( Spek 沒(méi)有Spock好用)。在2018年的冬天,每天與Kotlin相伴的幾個(gè)月后,我們總結(jié)出了正反兩面,并且得出Kotlin使我們的生產(chǎn)力下降的結(jié)論。我們開(kāi)始用Java重寫(xiě)這個(gè)微服務(wù)。

這有幾個(gè)原因:

  • 名稱(chēng)遮蔽
  • 類(lèi)型推斷
  • 編譯時(shí)空指針安全
  • 類(lèi)文字
  • 反向類(lèi)型聲明
  • 伴侶對(duì)象
  • 集合文字
  • 也許? 不
  • 數(shù)據(jù)類(lèi)
  • 公開(kāi)課
  • 陡峭的學(xué)習(xí)曲線(xiàn)

名稱(chēng)遮掩

這是 Kotlin 讓我感到***驚喜的地方??纯催@個(gè)函數(shù):

 

  1. fun inc(num : Int) { 
  2.     val num = 2 
  3.     if (num > 0) { 
  4.         val num = 3 
  5.     } 
  6.     println ("num: " + num) 

當(dāng)你調(diào)用inc(1)的時(shí)候會(huì)輸出什么呢?在Kotlin中方法參數(shù)是一個(gè)值,所以你不能改變num參數(shù)。這是好的語(yǔ)言設(shè)計(jì),因?yàn)槟悴粦?yīng)該改變方法的參數(shù)。但是你可以用相同的名稱(chēng)定義另一個(gè)變量,并按照你想要的方式初始化。現(xiàn)在,在這個(gè)方法級(jí)別的范圍中你擁有兩個(gè)叫做num的變量。當(dāng)然,同一時(shí)間你只能訪(fǎng)問(wèn)其中一個(gè)num,所以num的值會(huì)改變。將軍,無(wú)解了。

在if主體中,你可以添加另一個(gè)num,這并不令人震驚(新的塊級(jí)別作用域)。

好的,在Kotlin中,inc(1)輸出2。但是在Java中,等效代碼將無(wú)法通過(guò)編譯。

 

  1. void inc(int num) { 
  2.     int num = 2; //error: variable 'num' is already defined in the scope 
  3.     if (num > 0) { 
  4.         int num = 3; //error: variable 'num' is already defined in the scope 
  5.     } 
  6.     System.out.println ("num: " + num); 

名稱(chēng)遮蔽不是Kotlin發(fā)明的。這在編程語(yǔ)言中著很常見(jiàn)。在Java中,我們習(xí)慣用方法參數(shù)來(lái)遮蔽類(lèi)中的字段。

 

  1. public class Shadow { 
  2.     int val; 
  3.  
  4.     public Shadow(int val) { 
  5.         this.val = val; 
  6.     } 

在Kotlin中,遮蔽有點(diǎn)過(guò)分了。當(dāng)然,這是Kotlin團(tuán)隊(duì)的一個(gè)設(shè)計(jì)缺陷。IDEA團(tuán)隊(duì)試圖把每一個(gè)遮蔽變量都通過(guò)簡(jiǎn)潔的警告來(lái)向你展示,以此修復(fù)這個(gè)問(wèn)題:Name shadowed。兩個(gè)團(tuán)隊(duì)都在同一家公司工作,所以或許他們可以相互交流并在遮蔽問(wèn)題上達(dá)成一致共識(shí)?我感覺(jué)——IDEA是對(duì)的。我無(wú)法想象存在這種遮蔽了方法參數(shù)的有效用例。

類(lèi)型推斷

在Kotlin中,當(dāng)你申明一個(gè)var或者val時(shí),你通常讓編譯器從右邊的表達(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)化代碼。

例如,這段Kotlin代碼:

  1. var a = "10" 

將由Kotlin編譯器翻譯成:

  1. var a : String = "10" 

它曾經(jīng)是勝過(guò)Java的真正優(yōu)點(diǎn)。我故意說(shuō)曾經(jīng)是,因?yàn)?mdash;—有個(gè)好消息——Java10 已經(jīng)有這個(gè)功能了,并且Java10現(xiàn)在已經(jīng)可以使用了。

Java10 中的類(lèi)型涂端:

  1. var a = "10"

公平的說(shuō),我需要補(bǔ)充一點(diǎn),Kotlin在這個(gè)領(lǐng)域仍然略勝一籌。你也可以在其他上下文中使用類(lèi)型推斷,例如,單行方法。

更多關(guān)于Java10 中的 局部變量類(lèi)型推斷 。

編譯時(shí)空值安全

Null-safe 類(lèi)型是Kotlin的殺手級(jí)特征。 這個(gè)想法很好。 在Kotlin,類(lèi)型是默認(rèn)的非空值。 如果您需要一個(gè)可空類(lèi)型,您需要添加?符號(hào), 例如:

 

  1. val a: String? = null      // ok  
  2. val b: String = null       // 編譯錯(cuò)誤 

如果您在沒(méi)有空檢查的情況下使用可空變量,那么Kotlin將無(wú)法編譯,例如:

 

  1. println (a.length)          // compilation error 
  2. println (a?.length)         // fine, prints null 
  3. println (a?.length ?: 0)    // fine, prints 0 

一旦你有了這兩種類(lèi)型, non-nullable T 和nullable T?, 您可以忘記Java中最常見(jiàn)的異常——NullPointerException。 真的嗎? 不幸的是,事情并不是那么簡(jiǎn)單。

當(dāng)您的Kotlin代碼必須與Java代碼一起使用時(shí),事情就變得很糟糕了(庫(kù)是用Java編寫(xiě)的,所以我猜它經(jīng)常發(fā)生)。 然后,第三種類(lèi)型就跳出來(lái)了——T! 它被稱(chēng)為平臺(tái)類(lèi)型,它的意思是T或T?, 或者如果我們想要精確,T! 意味著具有未定義空值的 T類(lèi)型 。 這種奇怪的類(lèi)型不能用Kotlin來(lái)表示,它只能從Java類(lèi)型推斷出來(lái)。 T! 會(huì)誤導(dǎo)你,因?yàn)樗潘闪藢?duì)空的限制,并禁用了Kotlin的空值安全限制。

看看下面的Java方法:

 

  1. public class Utils { 
  2.     static String format(String text) { 
  3.         return text.isEmpty() ? null : text; 
  4.     } 

現(xiàn)在,您想要從Kotlin調(diào)用format(string)。 您應(yīng)該使用哪種類(lèi)型來(lái)使用這個(gè)Java方法的結(jié)果? 好吧,你有三個(gè)選擇。

***種方法。 你可以使用字符串,代碼看起來(lái)很安全,但是會(huì)拋出空指針異常。

 

  1. fun doSth(text: String) { 
  2.     val f: String = Utils.format(text)       // compiles but assignment can throw NPE at runtime 
  3.     println ("f.len : " + f.length) 

你需要用增加判斷來(lái)解決這個(gè)問(wèn)題:

 

  1. fun doSth(text: String) { 
  2.     val f: String = Utils.format(text) ?: ""  //  
  3.     println ("f.len : " + f.length) 

第二種方法。 您可以使用String?, 然后你的程序就是空值安全的了。

 

  1. fun doSth(text: String) { 
  2.     val f: String? = Utils.format(text)   // safe 
  3.     println ("f.len : " + f.length)       // compilation error, fine 
  4.     println ("f.len : " + f?.length)      // null-safe with ? operator 

第三種方法。 如果你讓Kotlin做了令人難以置信的局部變量類(lèi)型推斷呢?

 

  1. fun doSth(text: String) { 
  2.     val f = Utils.format(text)            // f type inferred as String! 
  3.     println ("f.len : " + f.length)       // compiles but can throw NPE at runtime 

壞主意。 這個(gè)Kotlin的代碼看起來(lái)很安全,也可以編譯通過(guò),但是允許空值在你的代碼中不受約束的游走,就像在Java中一樣。

還有一個(gè)竅門(mén),!! 操作符。 使用它來(lái)強(qiáng)制推斷f類(lèi)型為String類(lèi)型:

 

  1. fun doSth(text: String) { 
  2.     val f = Utils.format(text)!!          // throws NPE when format() returns null 
  3.     println ("f.len : " + f.length) 

在我看來(lái), Kotlin 的類(lèi)型系統(tǒng)中所有這些類(lèi)似scala的東西!,?和!!,實(shí)在是 太復(fù)雜了。 為什么Kotlin從Java的T類(lèi)型推斷到T! 而不是T?呢? 似乎Java互操作性破壞了Kotlin的殺手特性——類(lèi)型推斷。 看起來(lái)您應(yīng)該顯式地聲明類(lèi)型(如T?),以滿(mǎn)足由Java方法填充的所有Kotlin變量。

類(lèi) 字面量

在使用Log4j或Gson之類(lèi)的Java庫(kù)時(shí),類(lèi) 字面量 是很常見(jiàn)的。

在 Java 中,我們用.class后綴來(lái)寫(xiě)類(lèi)名:

  1. Gson gson = new GsonBuilder().registerTypeAdapter(LocalDate.class, new LocalDateAdapter()).create(); 

在 Groovy 中,類(lèi)字面量被簡(jiǎn)化為本質(zhì)。 你可以省略.class,不管它是Groovy還是Java類(lèi)都沒(méi)關(guān)系。

  1. def gson = new GsonBuilder().registerTypeAdapter(LocalDate, new LocalDateAdapter()).create() 

Kotlin區(qū)分了Kotlin和Java類(lèi),并為其準(zhǔn)備了不同的語(yǔ)法形式:

 

  1. val kotlinClass : KClass<LocalDate> = LocalDate::class 
  2. val javaClass : Class<LocalDate> = LocalDate::class.java 

所以在 Kotlin ,你不得不寫(xiě):

  1. val gson = GsonBuilder().registerTypeAdapter(LocalDate::class.java, LocalDateAdapter()).create() 

這真是丑爆了。

相反順序的類(lèi)型聲明

在C系列編程語(yǔ)言中,有一個(gè)標(biāo)準(zhǔn)的聲明類(lèi)型的方式。即先寫(xiě)出類(lèi)型,再寫(xiě)出聲明為該類(lèi)型的東西(變量、字段、方法等)。

在 Java 中如下表示:

 

  1. int inc(int i) { 
  2.     return i + 1; 

在 Kotlin 中則是相反順序的表示:

 

  1. fun inc(i: Int): Int { 
  2.     return i + 1 

這讓人覺(jué)得惱火,因?yàn)椋?/p>

首先,你得書(shū)寫(xiě)或者閱讀介于名稱(chēng)和類(lèi)型之間那個(gè)討厭的冒號(hào)。這個(gè)多余的字母到底起什么作用?為什么要把名稱(chēng)和類(lèi)型 分隔開(kāi) ?我不知道。不過(guò)我知道這會(huì)加大使用Kotlin的難度。

第二個(gè)問(wèn)題。在閱讀一個(gè)方法聲明的時(shí)候,你***想知道的應(yīng)該是方法的名稱(chēng)和返回類(lèi)型,然后才會(huì)去了解參數(shù)。

在 Kotlin 中,方法的返回類(lèi)型遠(yuǎn)在行末,所以可能需要滾動(dòng)屏幕來(lái)閱讀:

 

  1. private fun getMetricValue(kafkaTemplate : KafkaTemplate<String, ByteArray>, metricName : String) : Double { 
  2.     ... 

另一種情況,如果參數(shù)是按分行的格式寫(xiě)出來(lái)的,你還得去尋找返回類(lèi)型。要在下面這個(gè)方法定義中找到返回類(lèi)型,你需要花多少時(shí)間?

 

  1. @Bean 
  2. fun kafkaTemplate( 
  3.         @Value("\${interactions.kafka.bootstrap-servers-dc1}") bootstrapServersDc1: String, 
  4.         @Value("\${interactions.kafka.bootstrap-servers-dc2}") bootstrapServersDc2: String, 
  5.         cloudMetadata: CloudMetadata, 
  6.         @Value("\${interactions.kafka.batch-size}") batchSize: Int
  7.         @Value("\${interactions.kafka.linger-ms}") lingerMs: Int
  8.         metricRegistry : MetricRegistry 
  9. ): KafkaTemplate<String, ByteArray> { 
  10.  
  11.     val bootstrapServer = if (cloudMetadata.datacenter == "dc1") { 
  12.         bootstrapServersDc1 
  13.     } 
  14.     ... 

關(guān)于相反順序的 第三個(gè)問(wèn)題 是限制了IDE的自動(dòng)完成功能。在標(biāo)準(zhǔn)順序中,因?yàn)槭菑念?lèi)型開(kāi)始,所以很容易找到類(lèi)型。一旦確定了類(lèi)型,IDE 就可以根據(jù)類(lèi)型給出一些與之相關(guān)的變量名稱(chēng)作為建議。這樣就可以快速輸入變量名,不像這樣:

  1. MongoExperimentsRepository repository 

即時(shí)在 Intellij 這么優(yōu)秀的 IDE 中為 Kotlin 輸入這樣的變量名也十分不易。如果代碼中存在很多 Repository,就很難在自動(dòng)完成列表中找到匹配的那一個(gè)。換句話(huà)說(shuō),你得手工輸入完整的變量名。

  1. repository : MongoExperimentsRepository 

伴生對(duì)象

一個(gè) Java 程序員來(lái)到 Kotlin 陣營(yíng)。

“嗨,Kotlin。我是新來(lái)的,有靜態(tài)成員可用嗎?”他問(wèn)。

“沒(méi)有。我是面向?qū)ο蟮?,而靜態(tài)成員不是面向?qū)ο蟮模?rdquo; Kotlin回答。

“好吧,但我需要用于 MyClass 日志記錄器,該怎么辦?”

“沒(méi)問(wèn)題,可以使用伴生對(duì)象。”

“伴生對(duì)象是什么鬼?”

“它是與類(lèi)綁定的一個(gè)單例對(duì)象。你可以把日志記錄器放在伴生對(duì)象中,” Kotlin 如此解釋。

“明白了。是這樣嗎?”

 

  1. class MyClass { 
  2.     companion object { 
  3.         val logger = LoggerFactory.getLogger(MyClass::class.java) 
  4.     } 

“對(duì)!“

“好麻煩的語(yǔ)法,”這個(gè)程序看起來(lái)有些疑惑,“不過(guò)還好,現(xiàn)在我可以像這樣——MyClass.logger——調(diào)用日志記錄了嗎?就像在 Java 中使用靜態(tài)成員那樣?”

“嗯……是的,但是它不是靜態(tài)成員!它只是一個(gè)對(duì)象。可以想像那是一個(gè)匿名內(nèi)部類(lèi)的單例實(shí)現(xiàn)。而實(shí)際上,這個(gè)類(lèi)并不是匿名的,它的名字是 Companion,你可以省略這個(gè)名稱(chēng)。明白嗎?這很簡(jiǎn)單。”

我很喜歡 對(duì)象聲明 的概念——單例是種很有用的模式。從從語(yǔ)言中去掉靜態(tài)成員就不太現(xiàn)實(shí)了。我們?cè)贘ava中已經(jīng)使用了若干年的靜態(tài)日志記錄器,這是非常經(jīng)典的模式。因?yàn)樗皇且粋€(gè)日志記錄器,所以我們并不關(guān)心它是否是純粹的面向?qū)ο蟆V灰鹱饔?,而且不?huì)造成損害就好。

有時(shí)候,我們 必須 使用靜態(tài)成員。古老而友好的 public static void main() 仍然是啟動(dòng) Java 應(yīng)用的唯一方式。在沒(méi)有Google的幫助下嘗試著寫(xiě)出這個(gè)伴生對(duì)象。

 

  1. class AppRunner { 
  2.     companion object { 
  3.         @JvmStatic fun main(args: Array<String>) { 
  4.             SpringApplication.run(AppRunner::class.java, *args) 
  5.         } 
  6.     } 

集合字面量

在 Java 中初始化列表需要大量的模板代碼:

 

  1. import java.util.Arrays; 
  2. ... 
  3.  
  4. List<String> strings = Arrays.asList("Saab""Volvo"); 

初始化 Map 更加繁瑣,所以不少人使用 Guava :

 

  1. import com.google.common.collect.ImmutableMap; 
  2. ... 
  3.  
  4. Map<String, String> string = ImmutableMap.of("firstName""John""lastName""Doe"); 

我們?nèi)匀辉诘却?Java 產(chǎn)生新語(yǔ)法來(lái)簡(jiǎn)化集合和映射表的字面表達(dá)。這樣的語(yǔ)法在很多語(yǔ)言中都自然而便捷。

JavaScript:

 

  1. const list = ['Saab''Volvo'
  2. const map = {'firstName''John''lastName' : 'Doe'

Python:

 

  1. list = ['Saab''Volvo'
  2. map = {'firstName''John''lastName''Doe'

Groovy:

 

  1. def list = ['Saab''Volvo'
  2. def map = ['firstName''John''lastName''Doe'

簡(jiǎn)單來(lái)說(shuō),簡(jiǎn)潔的集合字面量語(yǔ)法在現(xiàn)代編程語(yǔ)言中倍受期待,尤其是初始化集合的時(shí)候。Kotlin 提供了一系列的內(nèi)建函數(shù)來(lái)代替集合字面量: listOf()、mutableListOf()、mapOf()、hashMapOf(),等等。

Kotlin:

 

  1. val list = listOf("Saab""Volvo"
  2. val map = mapOf("firstName" to "John""lastName" to "Doe"

映射表中的鍵和值通過(guò) to 運(yùn)算符關(guān)聯(lián)在一起,這很好,但是為什么不使用大家都熟悉的冒號(hào)(:)?真是令人失望!

Maybe?不

函數(shù)式編程語(yǔ)言(比如 Haskell)沒(méi)有空(null)。它們提供 Maybe Monad(如果你不清楚 Monad,請(qǐng)閱讀這篇由 Tomasz Nurkiewicz 撰寫(xiě) 文章 )。

在很久以前,Scala 就將 Maybe 作為 Option 引入 JVM 世界,然后在 Java 8 中被采用,成為 Optional。現(xiàn)在 Optional 廣泛應(yīng)用于 API 邊界,用于處理可能含空值的返回類(lèi)型。

Kotlin 中并沒(méi)有與 Optional 等價(jià)的東西。看起來(lái)你應(yīng)該使用 Kotlin 的可空類(lèi)型封裝。我們來(lái)研究一下這個(gè)問(wèn)題。

通常,在使用 Optional 時(shí),你會(huì)先進(jìn)行一系列空安全的轉(zhuǎn)換,***來(lái)處理空值。

比如在 Java 中:

 

  1. public int parseAndInc(String number) { 
  2.     return Optional.ofNullable(number) 
  3.                    .map(Integer::parseInt) 
  4.                    .map(it -> it + 1) 
  5.                    .orElse(0); 

在 Kotlin 中也沒(méi)問(wèn)題,使用 let 功能:

 

  1. fun parseAndInc(number: String?): Int { 
  2.     return number.let { Integer.parseInt(it) } 
  3.                  .let { it -> it + 1 } ?: 0 

可以嗎?是的,但并不是這么簡(jiǎn)單。上面的代碼可能會(huì)出錯(cuò),從 parseInt() 中拋出 NPE。只有值存在的時(shí)候才能執(zhí)行 Monad 風(fēng)格的 map(),否則,null 只會(huì)簡(jiǎn)單的傳遞下去。這就是 map() 方便的原因。然后不幸的是,Kotlin 的 let 并不是這樣工作的。它只是從左往右簡(jiǎn)單地執(zhí)行調(diào)用,不在乎是否是空。

因此,要讓這段代碼對(duì)空安全,你必須在 let 前添加 ?:

 

  1. fun parseAndInc(number: String?): Int { 
  2.     return number?.let { Integer.parseInt(it) } 
  3.                  ?.let { it -> it + 1 } ?: 0 

現(xiàn)在,比如 Java 和 Kotlin 兩個(gè)版本的可讀性,你更喜歡哪一個(gè)?

責(zé)任編輯:未麗燕 來(lái)源: 開(kāi)源中國(guó)翻譯文章
相關(guān)推薦

2025-04-23 08:22:37

JavaKotlin類(lèi)型

2022-07-15 13:01:13

Kotlin編程語(yǔ)言Java

2017-05-19 18:01:04

GoogleKotlin數(shù)據(jù)

2020-08-26 14:44:55

Java開(kāi)發(fā)代碼

2017-09-22 11:31:28

KotliJava編程語(yǔ)言

2022-02-28 10:38:13

Kotlin插件Android

2017-08-03 15:54:50

Kotlin繼承

2018-03-12 10:57:14

JavaKotlin語(yǔ)法

2018-01-03 11:51:06

KotlinTipsJava

2024-01-19 09:21:35

攜程開(kāi)源

2020-08-20 20:45:17

KotlinJava優(yōu)勢(shì)

2021-03-15 09:00:00

開(kāi)發(fā)JavaKotlin

2025-04-27 08:23:38

Kotlin協(xié)程管理

2020-06-01 09:30:25

代碼開(kāi)發(fā)Kotlin

2022-06-15 09:01:57

開(kāi)發(fā)Java

2023-11-15 16:37:46

2012-12-10 13:24:15

回歸分析數(shù)據(jù)挖掘

2012-03-31 10:49:18

ibmdw

2017-12-19 16:24:20

2019-08-28 18:46:33

KotlinAndroid語(yǔ)言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)