成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從Java到 Kotlin,再從Kotlin回歸Java

開發 后端
由于此博客文章引起高度關注和爭議,我們認為值得在Allegro上增加一些關于我們如何工作和做出決策的背景。Allegro擁有超過50個開發團隊可以自由選擇被我們PaaS所支持的技術。我們主要使用Java、Kotlin、Python和Golang進行編碼。本文中提出的觀點來自作者的經驗。

由于此博客文章引起高度關注和爭議,我們認為值得在Allegro上增加一些關于我們如何工作和做出決策的背景。Allegro擁有超過50個開發團隊可以自由選擇被我們PaaS所支持的技術。我們主要使用Java、Kotlin、Python和Golang進行編碼。本文中提出的觀點來自作者的經驗。

[[231717]]

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

我們嘗試過Kotlin,但現在我們正在用Java10重寫

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

這有幾個原因:

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

名稱遮掩

這是 Kotlin 讓我感到***驚喜的地方。看看這個函數:

 

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

當你調用inc(1)的時候會輸出什么呢?在Kotlin中方法參數是一個值,所以你不能改變num參數。這是好的語言設計,因為你不應該改變方法的參數。但是你可以用相同的名稱定義另一個變量,并按照你想要的方式初始化。現在,在這個方法級別的范圍中你擁有兩個叫做num的變量。當然,同一時間你只能訪問其中一個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發明的。這在編程語言中著很常見。在Java中,我們習慣用方法參數來遮蔽類中的字段。

 

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

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

類型推斷

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

例如,這段Kotlin代碼:

  1. var a = "10" 

將由Kotlin編譯器翻譯成:

  1. var a : String = "10" 

它曾經是勝過Java的真正優點。我故意說曾經是,因為——有個好消息——Java10 已經有這個功能了,并且Java10現在已經可以使用了。

Java10 中的類型涂端:

  1. var a = "10"

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

更多關于Java10 中的 局部變量類型推斷 。

編譯時空值安全

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

 

  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中最常見的異常——NullPointerException。 真的嗎? 不幸的是,事情并不是那么簡單。

當您的Kotlin代碼必須與Java代碼一起使用時,事情就變得很糟糕了(庫是用Java編寫的,所以我猜它經常發生)。 然后,第三種類型就跳出來了——T! 它被稱為平臺類型,它的意思是T或T?, 或者如果我們想要精確,T! 意味著具有未定義空值的 T類型 。 這種奇怪的類型不能用Kotlin來表示,它只能從Java類型推斷出來。 T! 會誤導你,因為它放松了對空的限制,并禁用了Kotlin的空值安全限制。

看看下面的Java方法:

 

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

現在,您想要從Kotlin調用format(string)。 您應該使用哪種類型來使用這個Java方法的結果? 好吧,你有三個選擇。

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

 

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

類 字面量

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

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

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

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

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

Kotlin區分了Kotlin和Java類,并為其準備了不同的語法形式:

 

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

所以在 Kotlin ,你不得不寫:

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

這真是丑爆了。

相反順序的類型聲明

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

在 Java 中如下表示:

 

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

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

 

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

這讓人覺得惱火,因為:

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

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

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

 

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

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

 

  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.     ... 

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

  1. MongoExperimentsRepository repository 

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

  1. repository : MongoExperimentsRepository 

伴生對象

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

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

“沒有。我是面向對象的,而靜態成員不是面向對象的,” Kotlin回答。

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

“沒問題,可以使用伴生對象。”

“伴生對象是什么鬼?”

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

“明白了。是這樣嗎?”

 

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

“對!“

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

“嗯……是的,但是它不是靜態成員!它只是一個對象。可以想像那是一個匿名內部類的單例實現。而實際上,這個類并不是匿名的,它的名字是 Companion,你可以省略這個名稱。明白嗎?這很簡單。”

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

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

 

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

我們仍然在等待 Java 產生新語法來簡化集合和映射表的字面表達。這樣的語法在很多語言中都自然而便捷。

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'

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

Kotlin:

 

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

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

Maybe?不

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

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

Kotlin 中并沒有與 Optional 等價的東西??雌饋砟銘撌褂?Kotlin 的可空類型封裝。我們來研究一下這個問題。

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

比如在 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。只有值存在的時候才能執行 Monad 風格的 map(),否則,null 只會簡單的傳遞下去。這就是 map() 方便的原因。然后不幸的是,Kotlin 的 let 并不是這樣工作的。它只是從左往右簡單地執行調用,不在乎是否是空。

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

 

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

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

責任編輯:未麗燕 來源: 開源中國翻譯文章
相關推薦

2025-04-23 08:22:37

JavaKotlin類型

2022-07-15 13:01:13

Kotlin編程語言Java

2017-05-19 18:01:04

GoogleKotlin數據

2022-02-28 10:38:13

Kotlin插件Android

2017-08-03 15:54:50

Kotlin繼承

2020-08-26 14:44:55

Java開發代碼

2017-09-22 11:31:28

KotliJava編程語言

2018-03-12 10:57:14

JavaKotlin語法

2024-01-19 09:21:35

攜程開源

2018-01-03 11:51:06

KotlinTipsJava

2025-04-27 08:23:38

Kotlin協程管理

2020-08-20 20:45:17

KotlinJava優勢

2021-03-15 09:00:00

開發JavaKotlin

2012-12-10 13:24:15

回歸分析數據挖掘

2020-06-01 09:30:25

代碼開發Kotlin

2022-06-15 09:01:57

開發Java

2023-11-15 16:37:46

2023-10-24 19:37:34

協程Java

2024-01-08 09:00:00

開發DSLKotlin

2012-03-31 10:49:18

ibmdw
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精精久久 | 黄色网络在线观看 | 成人国产在线观看 | 在线观看黄色大片 | 欧美国产在线一区 | 亚洲精品久久久久久久久久久 | 久久综合狠狠综合久久综合88 | 中文字幕 国产 | 欧美精品一区在线 | 亚洲国产精品久久久久婷婷老年 | 韩国久久 | 亚洲国产精品一区二区三区 | 一区二区三区中文字幕 | 久草精品视频 | 黄免费观看视频 | 成人在线免费电影 | 久久国产精品一区二区 | 日本精品久久久一区二区三区 | 日韩欧美在线观看视频 | 欧美一级黄 | 亚洲人人| 干干干操操操 | eeuss国产一区二区三区四区 | 日韩久久成人 | 国产精品爱久久久久久久 | 九色综合网 | 久久综合入口 | 久久久久久久久久久一区二区 | 久久久久国产一区二区三区 | 中文字幕在线一区二区三区 | 91不卡| 7777奇米影视 | 欧美一区二区三区国产精品 | 久在线视频播放免费视频 | 天天干狠狠操 | 久久久久久久综合 | 欧美日韩在线观看一区 | 超碰日本| 中文二区 | 久久精品免费一区二区三 | 日一日操一操 |