寫在前面
自從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á)式