日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
從Java到Kotlin,再從Kotlin回歸Java

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

成都創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站設(shè)計、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的滿洲網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!

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

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

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

這有幾個原因:

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

名稱遮掩

這是 Kotlin 讓我感到***驚喜的地方??纯催@個函數(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)的時候會輸出什么呢?在Kotlin中方法參數(shù)是一個值,所以你不能改變num參數(shù)。這是好的語言設(shè)計,因為你不應(yīng)該改變方法的參數(shù)。但是你可以用相同的名稱定義另一個變量,并按照你想要的方式初始化。現(xiàn)在,在這個方法級別的范圍中你擁有兩個叫做num的變量。當(dāng)然,同一時間你只能訪問其中一個num,所以num的值會改變。將軍,無解了。

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

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

 
 
 
  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); 

名稱遮蔽不是Kotlin發(fā)明的。這在編程語言中著很常見。在Java中,我們習(xí)慣用方法參數(shù)來遮蔽類中的字段。

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

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

類型推斷

在Kotlin中,當(dāng)你申明一個var或者val時,你通常讓編譯器從右邊的表達(dá)式類型中猜測變量類型。我們將其稱做局部變量類型推斷,這對程序員來說是一個很大的改進。它允許我們在不影響靜態(tài)類型檢查的情況下簡化代碼。

例如,這段Kotlin代碼:

 
 
 
  1. var a = "10" 

將由Kotlin編譯器翻譯成:

 
 
 
  1. var a : String = "10" 

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

Java10 中的類型涂端:

 
 
 
  1. var a = "10"; 

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

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

編譯時空值安全

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

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

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

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

一旦你有了這兩種類型, non-nullable T 和nullable T?, 您可以忘記Java中最常見的異?!狽ullPointerException。 真的嗎? 不幸的是,事情并不是那么簡單。

當(dāng)您的Kotlin代碼必須與Java代碼一起使用時,事情就變得很糟糕了(庫是用Java編寫的,所以我猜它經(jīng)常發(fā)生)。 然后,第三種類型就跳出來了——T! 它被稱為平臺類型,它的意思是T或T?, 或者如果我們想要精確,T! 意味著具有未定義空值的 T類型 。 這種奇怪的類型不能用Kotlin來表示,它只能從Java類型推斷出來。 T! 會誤導(dǎo)你,因為它放松了對空的限制,并禁用了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)該使用哪種類型來使用這個Java方法的結(jié)果? 好吧,你有三個選擇。

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

 
 
 
  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) 

你需要用增加判斷來解決這個問題:

 
 
 
  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做了令人難以置信的局部變量類型推斷呢?

 
 
 
  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 

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

還有一個竅門,!! 操作符。 使用它來強制推斷f類型為String類型:

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

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

類 字面量

在使用Log4j或Gson之類的Java庫時,類 字面量 是很常見的。

在 Java 中,我們用.class后綴來寫類名:

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

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

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

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

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

所以在 Kotlin ,你不得不寫:

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

這真是丑爆了。

相反順序的類型聲明

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

在 Java 中如下表示:

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

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

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

這讓人覺得惱火,因為:

首先,你得書寫或者閱讀介于名稱和類型之間那個討厭的冒號。這個多余的字母到底起什么作用?為什么要把名稱和類型 分隔開 ?我不知道。不過我知道這會加大使用Kotlin的難度。

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

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

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

另一種情況,如果參數(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 { 
  10.  
  11.     val bootstrapServer = if (cloudMetadata.datacenter == "dc1") { 
  12.         bootstrapServersDc1 
  13.     } 
  14.     ... 

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

 
 
 
  1. MongoExperimentsRepository repository 

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

 
 
 
  1. repository : MongoExperimentsRepository 

伴生對象

一個 Java 程序員來到 Kotlin 陣營。

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

“沒有。我是面向?qū)ο蟮模o態(tài)成員不是面向?qū)ο蟮?,?Kotlin回答。

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

“沒問題,可以使用伴生對象?!?/p>

“伴生對象是什么鬼?”

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

“明白了。是這樣嗎?”

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

“對!“

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

“嗯……是的,但是它不是靜態(tài)成員!它只是一個對象??梢韵胂衲鞘且粋€匿名內(nèi)部類的單例實現(xiàn)。而實際上,這個類并不是匿名的,它的名字是 Companion,你可以省略這個名稱。明白嗎?這很簡單?!?/p>

我很喜歡 對象聲明 的概念——單例是種很有用的模式。從從語言中去掉靜態(tài)成員就不太現(xiàn)實了。我們在Java中已經(jīng)使用了若干年的靜態(tài)日志記錄器,這是非常經(jīng)典的模式。因為它只是一個日志記錄器,所以我們并不關(guān)心它是否是純粹的面向?qū)ο?。只要它起作用,而且不會造成損害就好。

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

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

集合字面量

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

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

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

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

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

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'] 

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

Kotlin:

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

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

Maybe?不

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

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

Kotlin 中并沒有與 Optional 等價的東西??雌饋砟銘?yīng)該使用 Kotlin 的可空類型封裝。我們來研究一下這個問題。

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

比如在 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 中也沒問題,使用 let 功能:

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

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

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

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

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


當(dāng)前名稱:從Java到Kotlin,再從Kotlin回歸Java
文章來源:http://www.dlmjj.cn/article/dpepggc.html