Kotlin之心路歷程
以小白眼光看kotlin
kotlin這個小丫鬟被谷歌扶正為大房兩年,期間看過不少博文,很多人也已經把個人專案遷移到kotlin了,當然國外的開發者更給力,直接大部企業開發已經kotlin,也訂閱了濤哥的極客時間(一直沒時間看,果然看視訊太費事,還是文章可以抽得時間擠一擠學),一直是不想學習啊,一個人的惰性就是這樣,java又不是不能用,程式碼通俗易懂。說句心裡話,java8的lambda表示式我都沒學,ps:不過好像用java8 lambda表達示的在工作中也沒怎麼碰到。。
第一眼看到kotlin,大部分人都是,哇,我被它驚豔了(至少表面上都這麼說的),然而本渣狗並沒有感覺(難道就我一個菜狗?),反而覺得:啥?為了一個空安全學新的知識,if(x!=null)它不香哦。直到最近換了工作,遺留程式碼一半是kotlin,糟了,是心動的感覺。(不得不上手寫了)
其實最開始上手依然痛苦無比,lambda表示式各種省略,函式是一等公民這句廢話幾乎每個部落格都寫了,但是我看著太抽象啊,我管你幾等公民,你告訴我它區別在哪啊,有啥用啊。
無非下面一些問題
-
空安全並沒有讓我香到學習新知識
空安全確實沒有香到非學新東西不可,不過,它的設計思路還是不錯的。判空無非兩種做法,a. 在編寫程式碼裡執行時避免空指標異常,也就是常用的if(x!=null);b. 直接靜態程式碼檢查,在編譯期就避免了空指標,kotlin其實就是這種了,比如定義為String,那麼不好意思,別的地方你傳個null,編譯器就給你整紅,非要可空,那後果你自己負,用String?吧。這樣的好處是省略了大把邏輯判斷,讓編譯器幫我們把把關。
-
Lambda表示式省略一時爽,不會寫的人看不懂
最簡單的也是出現最多的,莫過於點選事件了
//kotlin view.setOnClickListener { //TODO } //java view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); 複製程式碼
大家其實看不習慣的原因就在於,沒有()帶引數,那我{}裡要使用()傳遞過來的引數怎麼辦,其實編譯器現在已經很智慧的告訴我們了
我們在{}中可以通過it就能引用這個傳遞過來的view引數。至於Lambda表示式為什麼能這麼省略,我們到後面再講。 -
函式是一等公民到底是個啥 無數次出現的函式是一等公民,到底講的是啥?其實就一句話,函式可以做引數傳遞給變數不就得了。。。熟悉前端語言的其實應該很熟。
-
程式碼中無故出現+-等運算子 運算子過載在很多lib中能看到,像經常用的協程庫就有
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main) 複製程式碼
我們自己寫的話,可以這麼寫
fun main() { println(Foo(1, 2) + Foo(3, 4)) } data class Foo(val x: Int, val y: Int) { operator fun plus(other: Foo): Foo = Foo(x + other.x, y + other.y) } 複製程式碼
運算子很多,可以參考https://www.jianshu.com/p/d445209091f0
-
炫技的中綴表示式 炫技的大哥們挺喜歡這麼寫
println("I" am "OK")
複製程式碼
或者一段中文style
println("你" 在 "幹嘛")
複製程式碼
這都是啥鬼喲,看看原始碼其實就好懂了,這就是一個簡單的語法糖
infix fun String.am(any: Any) {
}
infix fun String.在(any: Any) {
}
複製程式碼
其實就是infix來修飾的中綴表示式,擴充套件了String,增加am方法,就是這麼一回事而已,擴充套件函式搞不明白,後面會講到(這是我最喜歡kotlin的一點了)
- DSL浪用 舉例?要我這麼懶的人來舉例,怕是抬舉我了,在網上找一個,
showDialog {
title = "title"
message = "message"
rightClicks {
toast("clicked!")
}
}
複製程式碼
嗯嗯,這配置程式碼確實寫得很少簡潔了,但是對初學是一眼萬年(懵),dsl的動態性和高擴充套件性,讓kotlin有了更多的活力,但是DSL是要精而專,如果不到位的話,寫起來也沒輕鬆多少,當然,做公共元件,比如在這裡使用dialog的話,當你熟悉了的話,呼叫會快得多。
其實DSL說穿了就是擴充套件函式搞起,就那回事,主要是你要想得全,封裝得好
inline fun AppCompatActivity.showDialog(settings: CustomDialogFragment.() -> Unit) : CustomDialogFragment {
val dialog = CustomDialogFragment.newInstance()
dialog.apply(settings)
val ft = this.supportFragmentManager.beginTransaction()
val prev = this.supportFragmentManager.findFragmentByTag("dialog")
if (prev != null) {
ft.remove(prev)
}
ft.addToBackStack(null)
dialog.show(ft, "dialog")
return dialog
}
複製程式碼
上面這段程式碼擴充套件了Activity,所以我們可以直接呼叫showDialog函式,裡面唯一傳入的引數是個lambda表示式,所以可以把{}直接放後面,別的都省略(lambda表示式後面馬上就會講)。showDialog( )方法中只有唯一的引數settings,其型別是CustomDialogFragment.() -> Unit,即帶有CustomDialogFragment引數型別的函式。
在showDialog( )方法內部,構造了CustomDialogFragment物件,並呼叫dialog.apply(settings)方法,其作用即在構造Dialog物件後,對Dialog進行設定。在實際呼叫showDialog()方法時,就可以持有該CustomDialogFragment物件,然後呼叫CustomDialogFragment提供的public介面配置Dialog。
說一說香在哪
如果有朋友聽我嘍嗖到這裡的話,那麼說明我們都一樣,是時候上硬菜了。
擴充套件庫——協程
別給我整有的沒有,我最開始香起來還就是協程了(手動滑稽)。
其實協程並不是一個新概念,很多現在語言都是有滴,像python(人生苦短,我用python),go這些現代語言,都是有協程的,從計算機的發展史來說,有可能協程的概念比執行緒還出來得要早。不過無所謂了,我也不當大歷史家,現在我們使用的協程一般是線上程上的,雖然協程也可以直接執行在程式上,和執行緒毛關係都沒有,但是我們做Android的,天生就有一條主執行緒。
很多人使用Rxjava看重的是啥?就是rxjava切換執行緒,順滑如絲,那麼,你如果只使用了rxjava的這一點特性的話,協程就足夠了,效能還能直接提升不少。
概念太多,無非就是協程實際上就是極大程度的複用執行緒,通過讓執行緒滿載執行,達到最大程度的利用CPU,進而提升應用效能。我們知道,執行緒阻塞時,其實這條執行緒是閒置的,通過協程,我們可以在同一執行緒中執行多個協程任務,當這個協程任務掛起時,執行另外的協程,這樣,執行緒就拉滿了。
廢話太多了,一哈講不清,雖然有考慮後面會單獨拎出來講,但是好歹要給口糖,說下簡單使用。
因為是擴充套件庫,所以需要手動引入
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
複製程式碼
GlobalScope.launch(Dispatchers.Default) {
println(Thread.currentThread().name)
withContext(Dispatchers.Main) {
println(Thread.currentThread().name)
}
}
//執行結果
I/System.out: DefaultDispatcher-worker-1
I/System.out: main
複製程式碼
這樣就執行緒切換了,如果要在主執行緒等非同步執行緒工作完後拿到它的返回值呢
GlobalScope.launch(Dispatchers.Main) {
val job = async(Dispatchers.Default) {
println(Thread.currentThread().name)
"a"
}
println(job.await())
}
//控制檯列印
I/System.out: DefaultDispatcher-worker-1
I/System.out: a
複製程式碼
ps:不推薦使用GlobalScope,可以使用MainScope,至於原因以及使用方法,以前再講
同樣,不同協程前傳遞資料,也是可以使用傳遞資料的,提供了channel很容易建立一個生產者——消費者模型
runBlocking {
val channel = Channel<Int>(2)
GlobalScope.launch {
for (i in 1..5) {
channel.send(i)
}
channel.close()
}
for (a in channel) {
println(a)
}
println("done")
}
複製程式碼
使用runBlocking,是因為必須channel執行在一個掛起suspend函式或者協程裡,Channel<Int>(2)
引數2是一次傳送兩,預設是一個一個發。
協程先就只講這麼多了,先看下別的香法吧
擴充套件函式
擴充套件函式香到什麼地步,反正我是無法抗拒了。試問寫java的童子們,曾幾何時不都想擴充套件系統或者第三方類的方法時,只能含淚util,那麼,有了擴充套件函式的特性後,媽媽再也不用擔心我不能好好裝B了。
inline fun String.lastChar(): Char = this[this.length - 1]
fun main(){
println("abc".lastChar())
}
//////
// 輸出c
複製程式碼
就是因為擴充套件函式的天馬行空,才有了kotlin的自由自在,這時候我想到的是安迪在肖申克的高牆上,看著大夥享受著自己贏來的啤酒,這是自由的味道~
動態代理
什麼,一句話就搞掂了動態代理?沒錯,就辣麼輕鬆愜意
interface Base{
fun abc()
}
class BaseImpl(val x:Int):Base{
override fun abc() {
print(x)
}
}
class Derived(b:Base):Base by b
fun main(){
val b = BaseImpl(110)
Derived(b).abc()
}
// 控制檯列印輸出
// 110
複製程式碼
其中class Derived(b:Base):Base by b
就幫我們實現了動態代理
如果有興趣可以反編譯一下看java程式碼,其實kotlin是在編譯裡,會把我們的程式碼翻譯成靜態代理的程式碼,因為沒有使用反射,要比jdk動態代理的效率高得多,而且,這樣的程式碼寫起來不香麼?
真泛型
看到真泛型時,腦袋瓜子是不是嗡嗡的,其實我們在java中使用泛型時,是沒法拿到這個泛型的型別的,JVM的泛型都是通過型別擦除來實現的,也就是說泛型類例項的實參在編譯時被擦除,執行時不會被保留。kotlin執行在jvm上,一樣會遇到這個問題,所以kotlin想了一個好方法,並解決了這個問題。
fun main(){
testT<String>()
}
inline fun <reified T> testT(){
println(T::class.java)
}
////class java.lang.String
複製程式碼
可以看到,我們直接拿到了傳遞進來的泛型型別,像Gson這些庫,我們就完全可以重新封裝一下了。
Lambda表示式
lambda是一個函式表示式,是匿名函式,用來表示函式型別的例項的一種方式。
這裡要先了解一下高階函式
來了來了,在kotlin中,函式是一等公民。
高階函式是將函式用作引數或返回值的函式。
而lambda經常用在此處
Lambda 的語法規則是這樣的:
- lambda 表示式總是括在花括號中
- 完整語法形式的引數宣告放在花括號內,多引數的話用逗號隔開
- 函式體放在
->
之後 - 如果需要返回值,那麼函式體的最後一個表示式會被視為返回值
以如下幾條規則能夠讓 Lambda 的表示更加簡潔:
-
如果引數型別能被推匯出來,那麼可以省略的型別標註
-
如果 lambda 表示式的引數未使用,那麼可以用下劃線取代其名稱
-
如果 Lambda 只有一個引數並且編譯器能識別出型別,那麼可以不用宣告這個引數並忽略 ->。 該引數會隱式宣告為 it 。
-
如果函式的最後一個引數接受函式,那麼作為相應引數傳入的 lambda 表示式可以放在圓括號之外
-
如果該 lambda 表示式是呼叫時唯一的引數,那麼圓括號可以完全省略。
還是拿view的點選事件來舉例,用kotlin的完整寫法是:
view.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View?) { //TODO } }) 複製程式碼
根據java中只有單個非預設抽象介面,在kotlin可以使用函式來表示,這時,可以精簡為:
view.setOnClickListener({ v: View -> //TODO }) 複製程式碼
而lambda中唯一引數可以省略
view.setOnClickListener({ //TODO }) 複製程式碼
lambda表示式為唯一引數時,()可以省略,這樣就變成了
view.setOnClickListener{ //TODO } 複製程式碼
all啦
其實lambda表達示只是在高階函式中使用時,省略比較多我們用不習慣而已,習慣覺得挺爽的,不用再多寫一堆程式碼了
小結
總的來說,kotlin是越用越香的那種,最開始從java寫kotlin還真的曲線陡峭,罵罵娘不就好了,世間萬物,難逃真香定律。
下面是我的公眾號,歡迎大家關注我