Kotlin最佳工程實(shí)踐

寫在前面

自從Kotlin被官宣為Android開發(fā)正式語言畦粮,這門語言也越來越流行沧踏。相信大家也對(duì)Kotlin這門語言有過了解或者學(xué)習(xí),例如類型推斷签杈、空安全、lambda表達(dá)式、高階函數(shù)答姥,甚至Kotlin協(xié)程之類的铣除。但聽了很多大道理,還是過不好這一生鹦付;對(duì)很多java程序員來說尚粘,學(xué)了很多kotlin知識(shí),但真到自己寫的時(shí)候還是無從下手敲长,寫法還是照搬java那一套郎嫁。
本文就是為了解決這個(gè)問題,希望提供在工程實(shí)踐具體場景下如何去有效的利用kotlin的語言特性祈噪,接受kotlin的編程思想泽铛,來提高開發(fā)效率和優(yōu)化代碼結(jié)構(gòu)。此外辑鲤,本文中提到的工程實(shí)踐限定在Java和Kotlin混合工程盔腔,對(duì)于純Kotlin工程下一些最優(yōu)實(shí)踐不作討論。最后月褥,本文目標(biāo)受眾是學(xué)習(xí)過Kotlin的Java程序員弛随,很多東西會(huì)建立在Java和kotlin語言基礎(chǔ)上討論,不會(huì)介紹kotlin語言的基礎(chǔ)知識(shí)宁赤。

主要場景

如何實(shí)現(xiàn)懶漢式單例模式

眾所周知舀透,Kotlin中不在提供static關(guān)鍵字,而是提供了object關(guān)鍵字决左,可以更加方便的去聲明一個(gè)對(duì)象盐杂,而Java中的單例模式也可以簡化成一句簡單的聲明,示例代碼:

object SimpleSington {
  fun test() {}
}
//在Kotlin里調(diào)用
SimpleSington.test()

//在Java中調(diào)用
SimpleSington.INSTANCE.test();

這種聲明單例的方法哆窿,等價(jià)于如下java代碼:

public final class SimpleSington {
   public static final SimpleSington INSTANCE;

   private SimpleSington() {
   }

   static {
      INSTANCE = new SimpleSington();
   }
}

而這是餓漢式的實(shí)現(xiàn)链烈,有可能造成一定的性能浪費(fèi)。如果對(duì)于性能開銷沒有特別嚴(yán)格的要求挚躯,這個(gè)也滿足日常開發(fā)强衡,但如果因?yàn)闃I(yè)務(wù)、性能要求需要實(shí)現(xiàn)一個(gè)懶漢式的加載码荔,就需要改進(jìn)我們的寫法漩勤。這里可以用上kotlin的另一個(gè)語言特性:lazy。具體語法的介紹不是本文重點(diǎn)缩搅,如果需要了解可以查看:Kotlin的lateinit和lazy越败,下面是示例代碼:

class LazySingleton private constructor() {
    companion object {
        val instance: LazySingleton by lazy { LazySingleton() }
    }
}

這個(gè)寫法利用lazy關(guān)鍵字,非常簡單的實(shí)現(xiàn)了一個(gè)懶漢式的單例模式硼瓣。那么這段代碼等效于哪種java的寫法究飞;以及l(fā)azy的更高級(jí)用法置谦,包括3中LazyThreadSafetyMode的含義,讀者感興趣可以閱讀上面的鏈接并自己探究亿傅。

使用頂層函數(shù)代替靜態(tài)工具類

Java中static另一個(gè)典型應(yīng)用場景媒峡,就是靜態(tài)工具類,而在Kotlin中葵擎,我們應(yīng)該如何去寫一些工具類谅阿?對(duì)于Kotlin對(duì)static的替代,第一反應(yīng)是object或者companion object酬滤,那是不是應(yīng)該寫成如下這樣签餐?

// Don't
object StringUtil {
    fun countAmountOfX(string: String): Int{
        return string.length - string.replace("x", "").length
    }
}
StringUtil.countAmountOfX("xFunxWithxKotlinx")

但這樣的寫法我們并不推薦,這里應(yīng)該用Kotlin支持的頂層函數(shù)盯串,寫法如下:

// Do
fun countAmountOfX(str: String): Int {
    return str.length - str.replace("x", "").length
}
countAmountOfX("xFunxWithxKotlinx")

使用擴(kuò)展函數(shù)代替工具方法

其實(shí)上面的代碼還可以進(jìn)一步優(yōu)化氯檐,kotlin中可以使用擴(kuò)展函數(shù)來擴(kuò)展一個(gè)類的方法,這種寫法運(yùn)用在代碼中嘴脾,最終結(jié)果就成了這樣:

// Do
fun String.countAmountOfX(): Int {
    return length - replace("x", "").length
}
"xFunxWithxKotlinx".countAmountOfX()

這樣,這個(gè)函數(shù)就變成了像String自帶的函數(shù)一樣蔬墩,用起來非常自然译打;但在Kotlin/Java混合工程中,這樣用了頂層函數(shù)和擴(kuò)展函數(shù)語言特性的方法拇颅,要如何在Java里中使用呢奏司?這就需要加上一個(gè)注解,示例代碼如下:

@file:JvmName("StringUtil")

package com.liye.utils

// In Kotlin, remove the unnecessary wrapping util class and use top-level functions instead
// Often, you can additionally use extension functions, which increases readability ("like a story").
fun String.countAmountOfX(): Int {
    return length - replace("x", "").length
}

// Java code
StringUtil.countAmountOfX("xFunxWithxKotlinx")

這里面涉及到兩個(gè)知識(shí)點(diǎn):1. 通過@file:JvmName注解可以讓java中使用到kotlin的頂層函數(shù)樟插;2. kotlin中的擴(kuò)展函數(shù)在java中調(diào)用時(shí)韵洋,會(huì)把擴(kuò)展對(duì)象作為第一個(gè)參數(shù)傳入;
此外黄锤,根據(jù)Kotlin編碼規(guī)約的推薦搪缨,應(yīng)該把單行表達(dá)式的函數(shù)使用=來簡化,同時(shí)省去了返回值聲明鸵熟,代碼如下:

// Do
fun String.countAmountOfX() = length - replace("x", "").length

對(duì)于Kotlin的擴(kuò)展函數(shù)副编,還需了解的是Kotlin的標(biāo)準(zhǔn)庫中利用擴(kuò)展函數(shù)提供了大量基于Java類的擴(kuò)展,包括:String流强、Collection痹届、Array等,這些都可以在實(shí)踐中幫助我們更方便的編碼打月;而且Kotlin開發(fā)中也推薦盡量使用這些方法队腐,替代掉之前很多我們自己寫的各種工具類,例如:

// Do
if (str.isNullOrEmpty()) {
    // ....
}
arrayOf(1, 2, 3, 4)

使用默認(rèn)字段代替重載

Kotlin中的函數(shù)支持默認(rèn)參數(shù)奏篙,這在代碼聲明時(shí)可以替代掉原來需要靠重載編寫的大量代碼柴淘,示例如下:

// Don't
fun foo() = foo("a")
fun foo(a: String) { /*……*/ }

// Do
fun foo(a: String = "a") { /*……*/ }

但是Java并不支持默認(rèn)參數(shù),所以在混合工程中,Kotlin中使用了默認(rèn)參數(shù)的方法必須使用@JvmOverloads注解來支持java調(diào)用:

// Do
@JvmOverloads
fun foo(a: String = "a") { /*……*/ }

使用命名字段來增加可讀性

Kotlin支持在方法調(diào)用時(shí)悠就,使用命名參數(shù)千绪,示例如下:

val config2 = SearchConfig2(
       root = "~/folder",
       term = "game of thrones",
       recursive = true,
       followSymlinks = true
)

可以發(fā)現(xiàn),命名字段結(jié)合默認(rèn)字段梗脾,有點(diǎn)類似Java中使用了Builder模式或者流式調(diào)用的代碼荸型,如下:

// Don't
val config = SearchConfig()
       .setRoot("~/folder")
       .setTerm("game of thrones")
       .setRecursive(true)
       .setFollowSymlinks(true)

上述代碼是不推薦的,在Kotlin中炸茧,合理使用命名字段和默認(rèn)字段瑞妇,可以省去大量的這樣的set方法;

使用lambda替代callback

在java中梭冠,經(jīng)常需要使用callback接口來實(shí)現(xiàn)回調(diào)辕狰、觀察者模式等邏輯,在kotlin中更推薦使用lambda來替代callbcak控漠,省去callback接口的聲明與定義蔓倍;

// Don't
interface GreeterCallback {
    fun greetName(name: String): Unit
}

fun sayHi(callback: GreeterCallback) = /* … */

// Do
fun sayHi(callback: (String) -> Unit) = /* … */

// Caller
greeter.sayHi { Log.d("Greeting", "Hello, $it!") }

其中關(guān)于lambda的使用以及簡化可以參考文章:細(xì)說 Kotlin 的 Lambda 表達(dá)式

合理使用Kotlin的內(nèi)置函數(shù)(let、apply等)

提到Kotlin的lambda盐捷,不得不提kotlin中內(nèi)置了幾個(gè)以lambda作為參數(shù)傳入的內(nèi)置函數(shù)偶翅,這幾個(gè)函數(shù)結(jié)合kotlin的語言特性,可以大大簡化我們的代碼碉渡,下面給出幾個(gè)工程實(shí)踐中的例子聚谁。

替代if-null檢測

在java中為了運(yùn)行時(shí)避免NPE,我們經(jīng)常需要做非空檢測滞诺;而Kotlin一個(gè)重要的語言特性就是空安全形导,將非空檢測作為了語言機(jī)制的一部分,那么如何在kotlin中優(yōu)雅的去使用一些nullable對(duì)象呢习霹?比如下面這樣的代碼:

// Java
if (data != null && data.publishTime != null && viewHolder != null && viewHolder.commentTimeTxt != null) {
    viewHolder.commentTimeTxt.text = DateUtil.getRecommentDate(data.publishTime/1000)
}

在kotlin中就不需要再寫這么多if了朵耕,直接使用?.結(jié)合let即可搞定:

// Kotlin
data?.publishTime?.let {
            viewHolder?.commentTimeTxt?.text = DateUtil.getRecommentDate(it/1000)
        }

替代if-type檢測

了解Kotlin的基礎(chǔ)語法,就會(huì)知道kotlin中提供了is和as關(guān)鍵字來實(shí)現(xiàn)類似Java中經(jīng)常會(huì)使用的instanceOf和類型轉(zhuǎn)換淋叶。但很多時(shí)候憔披,結(jié)合內(nèi)置函數(shù),我們應(yīng)該更進(jìn)一步簡化我們的代碼爸吮,例如:

// Java
for (Animal animal : animals) {
    if (animal instanceOf Dog) {
        ((Dog) animal).bark();
    }
}

轉(zhuǎn)成Kotlin的話芬膝,可以寫成如下這樣:

// kotlin
animals.forEach {
    if (it is Dog) {
        it.bark()
    }
}

但其實(shí),還可以通過使用as?關(guān)鍵字進(jìn)一步簡化成如下代碼:

// kotlin
animals.forEach {
    (it as? Dog)?.bark()
}

整合對(duì)象初始化代碼

//Don't
val dataSource = BasicDataSource()
dataSource.driverClassName = "com.mysql.jdbc.Driver"
dataSource.url = "jdbc:mysql://domain:3309/db"
dataSource.username = "username"
dataSource.password = "password"
dataSource.maxTotal = 40
dataSource.maxIdle = 40
dataSource.minIdle = 4

這種Java中的常見寫法形娇,在kotlin中就不推薦了亮垫,可以用apply來大大簡化:

//Do
val dataSource = BasicDataSource().apply {
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://domain:3309/db"
    username = "username"
    password = "password"
    maxTotal = 40
    maxIdle = 40
    minIdle = 4
}

結(jié)語

基于Kotlin的新語法和編程思想西雀,還有很多的工程實(shí)踐可以探討如何從原來Java的寫法之上演進(jìn)寂诱,這里探討的只是很少一部分,更不用說還有類似Kotlin-reflect厨剪,kotlin-coroutine, RxKotlin這樣的庫,以及越來越多的支持庫友存。相信Kotlin作為新一代的安卓官方語言祷膳,會(huì)越來越擺脫Java的桎梏,發(fā)展的越來越好屡立,也希望大家可以盡快應(yīng)用到自己的工程中直晨。最后,歡迎討論與指正膨俐!

參考文獻(xiàn)

Kotlin的lateinit和lazy
Kotlin-Java interop guide
Kotlin習(xí)慣用法
Idiomatic Kotlin. Best Practices.
簡述Kotlin中l(wèi)et, apply, run, with的區(qū)別
細(xì)說 Kotlin 的 Lambda 表達(dá)式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末勇皇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子焚刺,更是在濱河造成了極大的恐慌敛摘,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乳愉,死亡現(xiàn)場離奇詭異兄淫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蔓姚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門捕虽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赂乐,你說我怎么就攤上這事薯鳍】” “怎么了挨措?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長崩溪。 經(jīng)常有香客問我浅役,道長,這世上最難降的妖魔是什么伶唯? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任觉既,我火速辦了婚禮,結(jié)果婚禮上乳幸,老公的妹妹穿的比我還像新娘瞪讼。我一直安慰自己,他們只是感情好粹断,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布符欠。 她就那樣靜靜地躺著,像睡著了一般瓶埋。 火紅的嫁衣襯著肌膚如雪希柿。 梳的紋絲不亂的頭發(fā)上诊沪,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音曾撤,去河邊找鬼端姚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挤悉,可吹牛的內(nèi)容都是我干的渐裸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼尖啡,長吁一口氣:“原來是場噩夢啊……” “哼橄仆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衅斩,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤盆顾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后畏梆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體您宪,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年奠涌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宪巨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡溜畅,死狀恐怖捏卓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慈格,我是刑警寧澤怠晴,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站浴捆,受9級(jí)特大地震影響蒜田,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜选泻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一冲粤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧页眯,春花似錦梯捕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至忿族,卻和暖如春锣笨,著一層夾襖步出監(jiān)牢的瞬間蝌矛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工错英, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留入撒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓椭岩,卻偏偏與公主長得像茅逮,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子判哥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355