Kotlin之心路歷程

apkcore發表於2019-09-18

Kotlin之心路歷程

以小白眼光看kotlin

kotlin這個小丫鬟被谷歌扶正為大房兩年,期間看過不少博文,很多人也已經把個人專案遷移到kotlin了,當然國外的開發者更給力,直接大部企業開發已經kotlin,也訂閱了濤哥的極客時間(一直沒時間看,果然看視訊太費事,還是文章可以抽得時間擠一擠學),一直是不想學習啊,一個人的惰性就是這樣,java又不是不能用,程式碼通俗易懂。說句心裡話,java8的lambda表示式我都沒學,ps:不過好像用java8 lambda表達示的在工作中也沒怎麼碰到。。

第一眼看到kotlin,大部分人都是,哇,我被它驚豔了(至少表面上都這麼說的),然而本渣狗並沒有感覺(難道就我一個菜狗?),反而覺得:啥?為了一個空安全學新的知識,if(x!=null)它不香哦。直到最近換了工作,遺留程式碼一半是kotlin,糟了,是心動的感覺。(不得不上手寫了)

其實最開始上手依然痛苦無比,lambda表示式各種省略,函式是一等公民這句廢話幾乎每個部落格都寫了,但是我看著太抽象啊,我管你幾等公民,你告訴我它區別在哪啊,有啥用啊。

無非下面一些問題

  1. 空安全並沒有讓我香到學習新知識

    空安全確實沒有香到非學新東西不可,不過,它的設計思路還是不錯的。判空無非兩種做法,a. 在編寫程式碼裡執行時避免空指標異常,也就是常用的if(x!=null);b. 直接靜態程式碼檢查,在編譯期就避免了空指標,kotlin其實就是這種了,比如定義為String,那麼不好意思,別的地方你傳個null,編譯器就給你整紅,非要可空,那後果你自己負,用String?吧。這樣的好處是省略了大把邏輯判斷,讓編譯器幫我們把把關。

  2. Lambda表示式省略一時爽,不會寫的人看不懂

    最簡單的也是出現最多的,莫過於點選事件了

    //kotlin
    view.setOnClickListener {
    	//TODO
    }
    
    //java
    view.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 
             }
         });
    複製程式碼

    大家其實看不習慣的原因就在於,沒有()帶引數,那我{}裡要使用()傳遞過來的引數怎麼辦,其實編譯器現在已經很智慧的告訴我們了

    Kotlin之心路歷程
    我們在{}中可以通過it就能引用這個傳遞過來的view引數。至於Lambda表示式為什麼能這麼省略,我們到後面再講。

  3. 函式是一等公民到底是個啥 無數次出現的函式是一等公民,到底講的是啥?其實就一句話,函式可以做引數傳遞給變數不就得了。。。熟悉前端語言的其實應該很熟。

  4. 程式碼中無故出現+-等運算子 運算子過載在很多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

  5. 炫技的中綴表示式 炫技的大哥們挺喜歡這麼寫

println("I" am "OK")
複製程式碼

或者一段中文style

println("你""幹嘛")
複製程式碼

這都是啥鬼喲,看看原始碼其實就好懂了,這就是一個簡單的語法糖

infix fun String.am(any: Any) {
}
infix fun String.在(any: Any) {
}
複製程式碼

其實就是infix來修飾的中綴表示式,擴充套件了String,增加am方法,就是這麼一回事而已,擴充套件函式搞不明白,後面會講到(這是我最喜歡kotlin的一點了)

  1. 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還真的曲線陡峭,罵罵娘不就好了,世間萬物,難逃真香定律。


我的CSDN

下面是我的公眾號,歡迎大家關注我

Kotlin之心路歷程

相關文章