Java老鳥如何玩轉(zhuǎn)Kotlin

前言

以一個java老鳥的角度氨距,如何去看 kotlin针贬。 Java源代碼應(yīng)該如何用Kotlin重構(gòu)。 如何正確學(xué)習(xí)kotlin并且應(yīng)用到實際開發(fā)中赋兵。本文將會探究笔咽。

本文分兩大塊,重難點和潛規(guī)則霹期。

重難點:Kotlin中可以獨立出來講解的大塊知識點叶组。提供單獨Demo。這部分大多數(shù)是Kotlin開創(chuàng)的新概念(相比于Java)历造。

潛規(guī)則:Kotlin是谷歌用來替換Java的甩十,它和java百分百完全兼容,但是實際上java轉(zhuǎn)成kotlin之后吭产,需要我們手動修改很多東西侣监,甚至某些部分必須打散重構(gòu)來達到最優(yōu)編碼。其中臣淤,kotlin的某些特性和java不同橄霉,甚至完全反轉(zhuǎn)。這部分知識點比較零碎邑蒋,單獨Demo不方便提供姓蜂,就以小例子的形式來寫。

正文大綱

  • 重難點

    • lambda以及操作符
    • 高階函數(shù)以及操作符
    • Kotlin泛型
    • 集合操作
    • 協(xié)程
    • 操作符重載
  • 潛規(guī)則

    • Kotlin文件和類不存在一對一關(guān)系

    • 共生體

    • 繼承

    • 修飾符

    • 空指針問題

正文

重難點

lambda表達式

lambda表達式是JDK1.8提出的寺董,目的是為了改善Java中大量出現(xiàn)的只有一個方法的回調(diào)函數(shù)的累贅寫法覆糟。這里不討論Java的lambda. Kotlin中l(wèi)ambda已經(jīng)融入代碼核心,而不是像java一樣是個注解+語法糖遮咖。

基礎(chǔ):

image.png

一個lambda表達式如圖:

lambda 表達式的4個部分

  • 外圍的{}大括號
  • 參數(shù)列表:x:Int,y:Int
  • 連接符 ->
  • 方法體 x+y

舉個栗子

這是一個kotlin文件:

/**
 * 比如說這樣滩字,我要給這個doFirst方法傳一個執(zhí)行過程,類型就是一個輸入2個Int御吞,輸出一個Int的拉姆達表達式
 */
fun calculate(p1: Int, p2: Int, event1: (Int, Int) -> Int, event2: (Int, Int) -> Int) {
    println("執(zhí)行doFirst")
    println("執(zhí)行event1 ${event1(p1, p2)}")
    println("執(zhí)行event2 ${event2(p1, p2)}")
}

//測試lambda表達式
fun main() {
    val sum = { x: Int, y: Int ->
        print("求和 ")
        x + y
    }
    val diff = { x: Int, y: Int ->
        print("求差 ")
        x - y
    }
    //kotlin里面麦箍,可以把拉姆達表示當作一個普通變量一樣,去傳遞實參
    calculate(p1 = 1, p2 = 2, event1 = sum, event2 = diff)
}

定義了一個calculate函數(shù), p1陶珠,p2 是Int挟裂,而event1和event2 則是 lambda表達式. 高級語言特性,一等函數(shù)公民:函數(shù)本身可以被當作普通參數(shù)一樣去傳遞揍诽,并且調(diào)用诀蓉。那么栗竖, kotlin的lambda內(nèi)核是怎么樣的?

image-20200103175246666.png

上圖能夠看出,

  1. calculate方法的后面2個參數(shù)渠啤,被編譯成了 Function2 類型狐肢。
  2. 執(zhí)行event1,event2沥曹,則是調(diào)用其invoke方法
  3. main函數(shù)中份名,出現(xiàn)了null.INSTANCE, 這里比較詭異,INSTANCE應(yīng)該是用來獲取實例的妓美,但是為什么是null.INSTANCE

而看了Function2的源碼僵腺,簡直亮瞎了我的鈦合金狗眼:

image.png

Function.kt文件中:

Function接口,F(xiàn)unction2接口.....Function22接口壶栋。好了辰如,不要質(zhì)疑谷歌大佬的設(shè)計思路,反正大佬寫的就是對的...這里用22個接口(至于為什么是22個委刘,猜想是谷歌覺得不會有哪個腦子秀逗的開發(fā)者真的弄22個以上的參數(shù)擠在一起吧)丧没,表示kotlin開發(fā)中可能出現(xiàn)的lambda表達式參數(shù)列表。

再舉個栗子

給一個Button 設(shè)置點擊事件,kotlin的寫法是:

val btn = Button(this)
val lis = View.OnClickListener { println("111") }
btn.setOnClickListener(lis)

或者:

val btn = Button(this)
btn.setOnClickListener { println("111") }

setOnClickListener 方法的參數(shù)是 OnClickListener 接口:

public interface OnClickListener {
    void onClick(View v);
}

類似這種符合lambda表達式特征的接口锡移,都可以使用上述兩種寫法來大大簡化代碼量呕童。

最后一個栗子

不得不提一句,lambda表達式有一個變形淆珊,那就是:當lambda表達式作為方法的最后一個參數(shù)時夺饲,可以lambda表達式放到小括號外面。而如果只有一個參數(shù)就是lambda表達式施符,那么括號可以省略

這個非常重要往声,不了解這個,很多地方都會感覺很蛋疼戳吝。

fun testLambda(s: String, block: () -> String) {
    println(s)
    block()
}

fun testLambda2(block: () -> String) {
    block()
}

fun main() {
    testLambda("第一個參數(shù)") {
        println("block函數(shù)體")
        "返回值"
    }
    testLambda2 {
        println("block函數(shù)體")
        "返回值"
    }
}

總結(jié)

Kotlin中l(wèi)ambda表達式可以當作普通參數(shù)一樣去傳遞浩销,去賦值,去使用听哭。


高階函數(shù)以及操作符

上文提到慢洋,kotlin中l(wèi)ambda表達式已經(jīng)融入了語言核心,而具體的體現(xiàn)就是高階函數(shù)陆盘,把lambda表達式當作參數(shù)去使用. 這種將lambda表達式作為參數(shù)或者返回值的函數(shù)普筹,就叫高階函數(shù)。

官方高階函數(shù)

Kotlin谷歌已經(jīng)給我們封裝了一些高階函數(shù)隘马。

  • run
  • with
  • apply
  • also
  • let
  • takeif 和 takeunless
  • repeat
  • lazy

run函數(shù)詳解

代碼如下(這里為了展示代碼全貌太防,忽視androidStudio的代碼優(yōu)化提示):

class A {
    val a = 1
    val b = "b"
}
fun testRun() {
    //run方法有兩種用法,一個是不依賴對象酸员,也就是作為全局函數(shù)
    run<String> {//我可以規(guī)定返回值的類型
        println("我是全局函數(shù)")
        "返回值"
    }
    val a = A()
    //另一種則是 依賴對象
    a.run<A,String> {//這里同樣可以規(guī)定返回值的類型
        println(this.a)
        println(this.b)
        "返回值"
    }
}
fun main() {
    testRun()
}

如上所示:

run函數(shù)分為兩類

  • 不依賴對象的全局函數(shù)蜒车。

  • 依賴對象的"類似"擴展函數(shù)讳嘱。

    兩者都可以規(guī)定返回值類型(精通泛型的話,這里應(yīng)該不難理解醇王,泛型下一節(jié)詳解)呢燥。

閱讀源碼:

@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

run函數(shù)被重載,參數(shù)有所不同

  • 前者 參數(shù)類型為 ()->R ,返回值為 R 寓娩,函數(shù)體內(nèi)執(zhí)行block(),并且返回執(zhí)行結(jié)果
  • 后者 參數(shù)類型為 T.()->R 呼渣,返回值為R 棘伴, T.() 明顯是依賴 T類的(貌似T的擴展函數(shù)),返回值依然是R屁置,執(zhí)行完之后返回結(jié)果焊夸。
  • 并且,可以發(fā)現(xiàn) 兩者都是內(nèi)聯(lián)函數(shù) inline (執(zhí)行代碼時不進行方法的壓棧出棧蓝角,而是類似于直接在目標處執(zhí)行代碼段)

所以阱穗,前者不需要依賴對象,后者必須依賴對象(因為它是T類的"擴展函數(shù)")

使用場景

根據(jù)run方法的特性使鹅,無論是不是依賴對象揪阶,它都是封裝了一段代碼,而且還是inline的患朱。所以:

  • 如果你不想把一段代碼獨立成方法鲁僚,又不想讓它們看上去這么凌亂,最重要的是告訴后來的開發(fā)者 這段代碼是一個整體裁厅,不要隨便給我在里面插代碼冰沙,或者拆分。那就用run方法执虹,把他們整合到一個作用域中拓挥。

    run {
        println("這是一段代碼邏輯很相近的代碼段")
        println("但是我不想把他獨立成一個方法")
        println("又擔心別人會隨便改")
        println("所以用run方法把他們放在同一個作用域中")
        println("小組中其他人看到這段,就知道不要把無關(guān)代碼插進來")
    }
    
  • 更神奇的是袋励,這個run函數(shù)是有返回值的侥啤,參數(shù)返回值可以利用起來:

    fun testRun2(param1: String, param2: String, param3: String) {
        //我想讓這幾個參數(shù)都不為空,如果檢查是空插龄,就不執(zhí)行方法主體
        val checkRes: Boolean = run<Boolean> {
            when {
                param1.isNullOrEmpty() -> {
                    false
                }
                param2.isNullOrEmpty() -> {
                    false
                }
                param3.isNullOrEmpty() -> {
                    false
                }
                else -> true
            }
        }
    
        if (checkRes){
            println("參數(shù)檢查完畢愿棋,現(xiàn)在可以繼續(xù)接下來的操作了")
        }else{
            println("參數(shù)檢查不通過,不執(zhí)行主體代碼")
        }
    }
    fun main() {
      testRun2("1", "2", "")
    }
    

main運行結(jié)果:

參數(shù)檢查完畢均牢,現(xiàn)在可以繼續(xù)接下來的操作了

小結(jié)論

run方法在整合小段代碼的功效上糠雨,還是很實用的

其他高階函數(shù)

上面列舉出來的這些系統(tǒng)高階函數(shù)原理上都差不多,只是使用場景有區(qū)別徘跪,因此除了run之外甘邀,其他的不再詳解琅攘,而只說明使用場景。

apply

和run只有一個區(qū)別松邪,run是返回block()執(zhí)行之后的返回值坞琴,而,apply 則是返回this逗抑,因此 apply必須依賴對象剧辐。而由于返回了this,因此可以連續(xù)apply調(diào)用邮府。

fun testApply() {
    val a = A()
    a.apply {
        println("如果一個對象要對它進行多個階段的處理")
    }.apply {
        println("那么多個階段都擠在一起則容易混淆荧关,")
    }.apply {
        println("此時可以用apply將每一個階段分開擺放")
    }.apply {
        println("讓程序代碼更加優(yōu)雅")
    }
}

fun main() {
    testApply()
}
with

下面的代碼應(yīng)該都很眼熟,Glide圖片加載框架的用法褂傀,with(context)然后鏈式調(diào)用

Glide.with(this).load(image).asGif().into(mImageView);

Kotlin中的with貌似把這一寫法發(fā)揚光大了(只是類比忍啤,不用深究),場景如下:

class A {
    val a = 1
    val b = "b"

    fun showA() {
        println("$a")
    }

    fun showB() {
        println("$a $b")
    }
}

fun testWith() {
    val a = A()
    with(a) {
        println("作用域中可以直接引用創(chuàng)建出的a對象")
        this.a
        this.b
        this.showA()
        this
    }.showB()
}

fun main() {
    testWith()
}

細節(jié)

  1. with(a){} 大括號內(nèi)的作用域里面仙辟,可以直接使用 當前a對象的引用同波,可以this.xxx 也可以 a.xxx
  2. with(a){} 大括號作用域內(nèi)的最后一行是 返回值,如果我返回this叠国,那么with結(jié)束之后未檩,我可以繼續(xù) 調(diào)用a的方法
also

also和with一樣,必須依賴對象煎饼,返回值為this讹挎。因此它也支持鏈式調(diào)用,它和apply的區(qū)別是:

image.png

apply的block吆玖,沒有參數(shù)筒溃,但是 also 則將this作為了參數(shù)。這么做造成的差異是:

作用域 { } 內(nèi)調(diào)用當前對象的方式不同沾乘。

class A {
    val a = 1
    val b = "b"

    fun showA() {
        println("$a")
    }

    fun showB() {
        println("$a $b")
    }
}
fun testApply() {
    A().apply {
        this.showA()
        println("=======")
    }.showB()
}

fun testAlso() {
    A().also {
        it.showA()
        println("=======")
    }.showB()
}

apply 必須用this.xxx, also則用 it.xxx.

let

類比到run:

public inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}
public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}

只有一個區(qū)別:run的block執(zhí)行不需要參數(shù)怜奖,而let 的block執(zhí)行時需要傳入this。

造成差異為:

A().run {
    println("最后一行為返回值")
    this
}.showA()

A().let {
    println("最后一行為返回值")
    it
}.showA()

run{} 作用域中 只能通過this.xxx操作當前對象翅阵,let 則用 it.xxx

takeif 和 takeunless

這兩個作用相反歪玲,并且他們必須依賴對象≈澜常看源碼:

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    return if (predicate(this)) this else null
}
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    return if (!predicate(this)) this else null
}

predicate 是 (T)->Boolean 類型的lambda表達式滥崩,表示斷言判斷,如果判斷為true讹语,則返回自身钙皮,否則返回空

class A {
    val a = 0
    val b = "b"

    fun showA() {
        println("$a")
    }

    fun showB() {
        println("$a $b")
    }
}

fun testTakeIfAndTakeUnless() {
    println("test takeIf")
    A().takeIf { it.a > 0 }?.showB()
    println("==========")
    println("test takeUnless")
    A().takeUnless { it.a > 0 }?.showB()
}

fun main() {
    testTakeIfAndTakeUnless()
}

執(zhí)行結(jié)果:

test takeIf
==========
test takeUnless
0 b

takeIf / takeUnless適用于將條件判斷的代碼包在一個作用域{}中,然后 用 ?.xxxx的安全調(diào)用符 來 執(zhí)行對象操作。

repeat

repeat是 多次循環(huán)的傻瓜版寫法短条。

fun testRepeat() {
    repeat(10) {
        print("$it ")
    }
}

fun main() {
    testRepeat()
}

執(zhí)行結(jié)果:

0 1 2 3 4 5 6 7 8 9 
lazy

lazy的作用是: 延遲初始化val定義的常量导匣。

class B {
    val i: Int by lazy {
        println("執(zhí)行i初始化")
        20
    }

    init {
        println("構(gòu)造函數(shù)執(zhí)行")
    }
}

如果只是初始化B對象,卻沒有使用到變量i, 那么延遲初始化不會執(zhí)行茸时。

fun main() {
    B()
}

執(zhí)行結(jié)果:

構(gòu)造函數(shù)執(zhí)行

而如果使用到了變量i贡定,才會去在調(diào)用之前初始化:

fun main() {
    println("i 變量的值是:" + B().i)
}

執(zhí)行結(jié)果:

構(gòu)造函數(shù)執(zhí)行
執(zhí)行i初始化
i 變量的值是:20

總結(jié)

Kotlin官方提供的高階函數(shù),run可都,apply缓待,also,let汹粤,with等命斧,旨在使用{}作用域,將聯(lián)系緊密的代碼封在一個作用域內(nèi)嘱兼,讓一個方法內(nèi)部顯得 有條不紊,閱讀觀感更好贤徒,可維護性更高芹壕,代碼更優(yōu)雅。上述接奈,除了lazy之外踢涌,所有的 函數(shù)都在Standart.kt文件內(nèi)部。


Kotlin泛型

類型擦除

我們在編碼java的時候序宦,寫一個泛型類睁壁,可能是這樣的

class Plate<T>{
    T t;
 
    Plate(T t){
        this.t = t;
    }
    
    T get(){
        return t;
    }
    
    void set(T t){
        this.t =
            t;
    }
}

以上是java代碼。

Java泛型是偽泛型互捌,在編譯之后潘明,所有的泛型寫法都會被移除,而會用實際的類型去替換秕噪。

mian函數(shù)運行的時候钳降,<T> 被移除。而原來的T腌巾,就變成了Object遂填。

所以,Plate的字節(jié)碼反編譯過來就應(yīng)該是

class Plate{
    Object t;
 
    Plate(Object t){
        this.t = t;
    }
    
    Object get(){
        return t;
    }
    
    void set(Object t){
        this.t = t;
    }
}

那么既然運行的時候澈蝙,泛型限制全都沒有了吓坚。那么怎么保證 泛型的作用呢?

答案:編碼的時候灯荧,編譯器幫我們進行校驗礁击。

strList.add(123);//報錯

PECS 法則 和 上下邊界的問題

public class Panzi<T> {
    T mT;
    public Panzi(T t) { mT = t;}
    public T get() { return mT; }
    public void set(T t) {  mT = t; }
}

class Fruit {}
class Banana extends Fruit {}
class Apple extends Fruit {}
 Panzi<Apple> applePanzi = new Panzi<>(new Apple());
 Panzi<Fruit> fruitPanzi = new Panzi<>(new Fruit());
 fruitPanzi = applePanzi;

雖然 Apple和Fruit是父子繼承關(guān)系。但是Panzi<Apple>Panzi<Fruit>是半毛錢關(guān)系都沒有,如果你想fruitPanzi = applePanzi ,把后者賦值給前者客税,是會報編譯錯誤的况褪。

如果想讓裝水果的盤子和 裝 蘋果的盤子發(fā)生一點關(guān)系,能夠后者賦值給前者.

Panzi<Apple> applePanzi = new Panzi<>(new Apple());
Panzi<? extends Fruit> fruitPanzi = new Panzi<>(new Fruit());
fruitPanzi = applePanzi;

那就必須使用到上下邊界的關(guān)鍵字 extends

extends 在泛型中表示指定上界更耻,也就是說测垛,實際類型都必須在Fruit之下(包括Fruit自己)。那么既然apple也是Fruit的子類秧均,那么賦值就可以做到食侮。

  • PECS法則

    剛才說到了上邊界 extends。而下邊界是 super關(guān)鍵字

    Panzi<? extends Fruit> extendsFruit = new Panzi<>(new Apple());
    Panzi<? super Fruit> superFruit = new Panzi<>(new Fruit());
    

    super關(guān)鍵字目胡,在泛型中表示定義下界锯七,實際類型必須在Fruit之上,同時也在Object之下(包括Fruit和Object)

    所以會出現(xiàn)這么一個情況:

    Panzi<? extends Fruit> extendsFruit = new Panzi<>(new Apple());
    Panzi<? super Fruit> superFruit = new Panzi<>(new Object());
    

    我們有這么兩個Panzi誉己,前者是 Fruit作為泛型上界眉尸,一個是Fruit作為下界。

    現(xiàn)在巨双,我們從Panzi中去調(diào)用get/set方法噪猾。會發(fā)現(xiàn)。

    PE:

     extendsFruit.set(new Apple()); // 編譯報錯筑累!
     Fruit fruit2 = extendsFruit.get();// 編譯正常
    

    為何袱蜡?因為 Fruit作為上界,我get出來的類型可以確定一定是Fruit類型的慢宗。但是我set進去的時候坪蚁,JVM無法判定實際類型(因為泛型被擦除,JVM只人為set(Object t) 的參數(shù)是一個Object)镜沽,JVM要求是Object敏晤,但是你卻給了一個Apple,編譯器無法處理淘邻。所以干脆 java的泛型茵典,? extends 定義了上界,只允許get宾舅,不允許set统阿。這就是PECS中的PE,意思就是 Pruducer Extends 筹我,生產(chǎn)者 Extends扶平,只取不存。

    相對應(yīng):

    CS: 則是 Cunsumer super 消費者只存不取蔬蕊。

    Object object = superFruit.get(); //get结澄,get出來雖然不報錯,但是沒有任何意義。因為不能確定類型麻献,只知道是一個Object们妥,無法調(diào)用API
    superFruit.set(new Fruit());// 但是set進去的時候,可以確定一定是一個Fruit的
    

這就是java泛型的 PECS法則.

kotlin泛型使用實例

java泛型里面比較糾結(jié)的難點就是類型擦除和PECS法則了勉吻。

那么kotlin泛型监婶,原理上和java泛型和沒有區(qū)別齿桃。只是寫法上有了區(qū)別惑惶。

open class Fruit

class Apple : Fruit()

class Banana : Fruit()

class Panzi<T>(t: T) {

    var t: T = t

    fun get(): T {
        return t
    }

    fun set(t: T) {
        this.t = t
    }
}
    fun test1() {
        // 試試能不能相互賦值
        var fruitPanzi: Panzi<Fruit> = Panzi(Fruit()) //一個水果盤子
        var applePanzi: Panzi<Apple> = Panzi(Apple()) //一個蘋果盤子
        //試試相互賦值
        //        fruitPanzi = applePanzi  // 編譯報錯
        //        applePanzi = fruitPanzi  // 編譯報錯
        //雙方完全是不相干的類,不能相互賦值 ,
    }

    /**
     * 加邊界之后再賦值
     */
    fun test2() {
        //如果你非要認為蘋果盤子歸屬于水果盤子短纵,那么可以這樣
        var fruitPanzi2: Panzi<out Fruit> = Panzi(Fruit()) //一個水果盤子
        var applePanzi2: Panzi<Apple> = Panzi(Apple()) //一個蘋果盤子

        fruitPanzi2 = applePanzi2  //那么這就是out決定泛型上邊界的案例
    }

    /**
     * PECS法則带污,OUT表示 ? extends 決定上界,上界生產(chǎn)者只取不存
     */
    fun test3() {
        //看一下get set方法
        // 決定上界之后的泛型香到,只取不存
        var fruitPanzi2: Panzi<out Fruit> = Panzi(Fruit())
        fruitPanzi2.get()
        //        fruitPanzi2.set(Apple())  // 這里編譯報錯鱼冀,和java泛型的表現(xiàn)一樣
    }

    /**
     * PECS法則,IN表示 ? super 決定下界悠就,下界消費者雷绢,只存不取
     */
    fun test4() {
        //試試泛型下界 in
        var fruitPanzi: Panzi<in Fruit> = Panzi(Fruit())
        fruitPanzi.set(Fruit())//可以set,但是看看get
        val get = fruitPanzi.get()//不會報錯理卑,get出來的類型就完全不能確定了瞬捕,只知道是 頂級類Any? 的子類,獲得它也沒有意義

    }

集合操作

Kotlin的集合扇单,并沒有重新開創(chuàng)一套規(guī)則,它的底層依然是java的Collection爆土。Kotlin提供了可變集合和不可變集合的接口鹉究。

  • 不可變集合:List宇立,SetMap (內(nèi)部元素不可以 增減 或者 修改自赔,在定義的時候就已經(jīng)將容量和內(nèi)部元素定死)

  • 不可變集合: MutableList , MutableSet,MutableMap(聲明的時候可以隨意指定初始值妈嘹,后續(xù)可以隨意增刪和修改內(nèi)部元素)

集合操作分為:對象的創(chuàng)建api的調(diào)用

對象的創(chuàng)建

方式有多種,以不可變集合 List 為例绍妨,kotlin的List底層和Java的List一致润脸,底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組。

靜態(tài)指定元素值

fun main() {
    val listOf = listOf<String>("str1", "str2", "str3")
    listOf.forEach { print("$it ") }
}

執(zhí)行結(jié)果:

str1 str2 str3

通過動態(tài)創(chuàng)建過程來指定元素值

fun main() {

    val list = List(3) {
        "str$it"
    }
    list.forEach { print("$it ") }
}

執(zhí)行結(jié)果:

str0 str1 str2 

api的調(diào)用

對象已經(jīng)創(chuàng)建他去,我們要利用kotlin提供的方法來完成業(yè)務(wù)代碼毙驯。

一級難度api(all,any灾测,count爆价,find,groupBy)
fun testCollectionFun() {
    val ages = listOf<Int>(1, 2, 3, 4, 5, 6, 7, 100, 200)
    //那么是不是所有的元素都大于10

    ages.apply { print("all:") }.all { it > 10 }.apply { println(this) } //結(jié)果是false

    //是不是存在任意一個元素大于10
    ages.apply { print("any:") }.any { it > 10 }.apply { println(this) }

    // 符合指定條件的元素個數(shù)
    ages.apply { print("count:") }.count { it < 10 }.apply { println(this) }

    //找到第一個符合條件的元素
    ages.apply { print("find:") }.find { it > 10 }.apply { println(this) }

    // 按照條件進行分組
    val groupBy = ages.apply { print("groupBy:") }.groupBy { it > 10 }
    groupBy[false].apply { println(this) }
    groupBy[true].apply { println(this) }

}

fun main() {
    testCollectionFun()
}

針對數(shù)組元素的簡單判斷,上述提供了簡明的示例代碼铭段,用List為例骤宣,至于Set和Map類似⌒蛴蓿可以自主去推斷寫法憔披。

執(zhí)行結(jié)果:

all:false
any:true
count:7
find:100
groupBy:[1, 2, 3, 4, 5, 6, 7]
[100, 200]
**二級難度api **(filter,map,flatMap,flatten)
  • filter和map
fun testCollectionFun2() {
    //二級難度api
    val ages = listOf<Int>(1, 2, 3, 4, 5, 6, 7, 100, 200)
    // 只保留大于10的元素,并返回一個新數(shù)組
    ages.filter { it > 10 }.apply { println(this) }
    //遍歷List的所有元素,根據(jù)條件返回值展运,創(chuàng)建新的元素內(nèi)容并放到新List中返回出來
    ages.map { if (it > 10) "大于10" else "小于等于10" }.apply { println(this) }
}
fun main() {
    testCollectionFun2()
}

執(zhí)行結(jié)果:

[100, 200]
[小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 大于10, 大于10]
  • flatMap活逆,因為稍復(fù)雜
// 比如一個學(xué)生,作為一個實體
class Student(name: String, math: Int, chinese: Int) {
    val name: String = name
    val score = Score(math, chinese)
}

//學(xué)生的成績分數(shù)作為一個主體
class Score(math: Int, chinese: Int) {
    val math: Int = math
    val chinese: Int = chinese
}

fun testFlatMap() {
    val students = listOf(
            Student("zero", 100, 80),
            Student("alvin", 70, 70),
            Student("lance", 90, 60)
    )
    //我只想統(tǒng)計所有的數(shù)學(xué)成績的總分和平均分,怎么辦
    students.flatMap { listOf(it.score.math) }.apply {
        var total = 0
        this.forEach {
            total += it
        }
        println("數(shù)學(xué)成績的總分是:$total  平均分是:${total / this.size}")
    }
}

fun main() {
    testFlatMap()
}

執(zhí)行結(jié)果:

數(shù)學(xué)成績的總分是:260  平均分是:86

當面對復(fù)雜數(shù)據(jù)結(jié)構(gòu)時拗胜,我們想要提煉出其中的某一層數(shù)據(jù)蔗候,并不關(guān)心其他無關(guān)字段。就適合使用 flatMap 進行扁平化提煉埂软。

  • flatten

意味:"平鋪"

fun testFlatten(){
    val list = listOf(listOf("Str1","Str3"), listOf("Str4","Str2"))
    val listsOfList = list.flatten()
    println("平鋪之前:$list \n平鋪之后$listsOfList")
}

fun main() {
    testFlatten()
}

執(zhí)行結(jié)果:

平鋪之前:[[Str1, Str3], [Str4, Str2]] 
平鋪之后[Str1, Str3, Str4, Str2]

在存在List嵌套的情況下锈遥,flatten可以把復(fù)合式的數(shù)據(jù)結(jié)構(gòu) 變成 扁平式的。它和flatMap不同勘畔,flatMap適合在復(fù)雜數(shù)據(jù)結(jié)構(gòu)中在指定的層級形成一個集合所灸,方便統(tǒng)計和計算。而flatten則更適用于類型相似的集合嵌套扁平化操作炫七。適用場景還是有差別的爬立。


協(xié)程

想了很久,關(guān)于協(xié)程的內(nèi)容万哪,在官網(wǎng)上確實有很多內(nèi)容侠驯,基礎(chǔ)知識概念,基本使用奕巍,以及 流操作吟策,通道,異常處理的止,并發(fā)處理等檩坚,不方便在這里展開。具體的參照:Kotlin中文網(wǎng)

image.png

這里具體去學(xué)習(xí)诅福。本章節(jié)匾委,只總結(jié)一下近期查閱資料并經(jīng)本人驗證的知識點。

概念

英文 coroutines : /,k?uru:'ti:n/ 意: 協(xié)同程序权谁。 簡稱協(xié)程剩檀。

Kotlin提出協(xié)程概念,是為了簡化異步編程旺芽,讓開發(fā)者更容易控制函數(shù)的執(zhí)行流程沪猴。

協(xié)程和線程的聯(lián)系和區(qū)別

在操作系統(tǒng)OS中辐啄,進程資源分配的最小單位線程任務(wù)調(diào)度的最小單位运嗜。而協(xié)程則是處在線程內(nèi)部的“微線程”壶辜,或者說輕量級線程。 由于線程在OS中是稀缺資源担租,所有OS平臺的線程數(shù)量都是有上限的砸民,平時編程我們會用線程池來管理線程數(shù)量,熟悉線程池的同學(xué)應(yīng)該知道奋救,線程池管理線程岭参,無論是核心線程還是非核心線程,都不會隨意去創(chuàng)建尝艘,除非迫不得已演侯。

線程解決異步問題

  • 多線程同步編程可以通過加鎖解決數(shù)據(jù)的線程安全問題,但是加鎖會降低程序執(zhí)行效率背亥,并且鎖多了秒际,會有死鎖隱患
  • 線程的狀態(tài)轉(zhuǎn)換完全由內(nèi)核控制,程序員開發(fā)者無法干涉
  • 線程的是稀缺資源狡汉,不能隨意創(chuàng)建娄徊,使用線程解決異步問題,線程的初始化盾戴,上下文切換(CPU輪轉(zhuǎn))寄锐,線程狀態(tài)切換(sleep,yield...), 變量加鎖操作(synchronized關(guān)鍵字)尖啡,都會使得線程的使用代價比較大

協(xié)程解決異步問題

  • 協(xié)程是運行在線程之上的優(yōu)化產(chǎn)物锐峭,或稱“微線程”。協(xié)程依賴線程運行可婶,復(fù)雜的底層邏輯被封裝在庫內(nèi),使用時無需關(guān)心所處線程狀態(tài)
  • 使用協(xié)程援雇,開發(fā)者可以自己控制協(xié)程的狀態(tài)(suspend掛起矛渴,resume恢復(fù)),而不會像線程那樣依賴底層調(diào)度惫搏,時間片爭奪具温。
  • 一個線程可以跑多個協(xié)程,一個協(xié)程也可以分段在多個線程上執(zhí)行
  • 協(xié)程 是 非阻塞的筐赔,當前協(xié)程掛起之后铣猩,所在線程資源并不會浪費,它會去執(zhí)行其他協(xié)程(如果有的話)
  • 協(xié)程 相對于線程這種OS中的稀缺資源茴丰,它是極其輕量級的达皿,就算你開一百萬個協(xié)程天吓,對于系統(tǒng)的壓力也不會像大量線程那樣大(別說一百萬個,linux系統(tǒng)的線程數(shù)量上線是1000峦椰,超過這個值系統(tǒng)就無法正常運行).
  • 總之一句話 : 協(xié)程的出現(xiàn)龄寞,讓程序開發(fā)者對程序邏輯的掌控提升到了一個新的境界,想象一下汤功,一個函數(shù)正在執(zhí)行物邑,你想讓他在某個時刻暫停,然后在另一個時刻繼續(xù)滔金。利用線程恐怕很難做到色解。協(xié)程中,輕而易舉餐茵。

基本使用

module級別的build.gradle 中 引入庫依賴,推薦使用最新版(目前穩(wěn)定版是1.3.3)

dependencies {
    //...
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3" // 如果你需要用到協(xié)程調(diào)度器Dispatchers的話,必須加上這個依賴
}
協(xié)程的創(chuàng)建

創(chuàng)建協(xié)程有多種方式科阎,全局/獨立,同步/異步钟病,并且可以指定 "協(xié)程調(diào)度器"

  • runBlocking

    fun main() {
        runBlocking {
            println("這是runBlocking協(xié)程...")
        }
    }
    
  • launch

    fun main() {
        runBlocking {
            println("這是runBlocking協(xié)程...")
            launch {
                println("這是runBlocking內(nèi)部的runBlocking協(xié)程...")
                delay(2000)
                println("延遲了2000MS之后萧恕,再打印")
            }
        }
    }
    
  • GlobalScope.launch

    fun main() {
        println("協(xié)程,相對于主線程來說肠阱,都是異步的票唆,也就是說,你在這里插入?yún)f(xié)程邏輯屹徘,主線程的邏輯并不會被阻塞")
        GlobalScope.launch {
            delay(3000)
            println("GlobalScope.launch 創(chuàng)建協(xié)程 ")
        }
        println("主線程繼續(xù)")
        Thread.sleep(5000)
    }
    
  • Global.async

    fun main() {
        runBlocking {
            val async: Deferred<String> = GlobalScope.async {
                println("這是一個異步協(xié)程走趋,他將返回一個Deferred")
                delay(2000)
                "異步任務(wù)返回值"
            }
            println("主線程繼續(xù):" + async.await())
        }
    
        Thread.sleep(5000)
    }
    
“騷操作”

關(guān)心協(xié)程的人一般都會十分關(guān)注它到底能給我們異步編程帶來怎樣的便利。這里總結(jié)幾個不用協(xié)程實現(xiàn)起來很麻煩的騷操作噪伊。

  • 如果有一個函數(shù)簿煌,它的返回值需要等到多個耗時的異步任務(wù)都執(zhí)行完畢返回之后,組合所有任務(wù)的返回值作為 最終返回值

    fun test6(): String = runBlocking {
        var finalRes = ""
        coroutineScope {
            launch {
                delay(1000)
                finalRes = finalRes.plus("1")
            }
            launch {
                delay(2000)
                finalRes = finalRes.plus("2")
            }
    
            launch {
                delay(3000)
                finalRes = finalRes.plus("3")
            }
        }
        finalRes
    }
    
    fun main() {
        val test6 = test6()
        println("最終返回值是: $test6")
    }
    

    最終返回結(jié)果為(延遲3秒之后打印):

    最終返回值是: 123
    
  • 如果有一個函數(shù)鉴吹,需要順序執(zhí)行多個網(wǎng)絡(luò)請求姨伟,并且后一個請求依賴前一個請求的執(zhí)行結(jié)果

    import kotlinx.coroutines.*
    
    suspend fun getToken(): String {
        for (i in 0..10) {
            println("異步請求正在執(zhí)行:getToken :$i")
            delay(100)
        }
        return "ask"
    }
    
    suspend fun getResponse(token: String): String {
        for (i in 0..10) {
            println("異步請求正在執(zhí)行:getResponse :$token $i")
            delay(100)
        }
    
        return "response"
    }
    
    fun setText(response: String) {
        println("setText 執(zhí)行,時間:  ${System.currentTimeMillis()}")
    }
    
    fun main() {
        GlobalScope.launch(Dispatchers.Unconfined) {
            var token = GlobalScope.async(Dispatchers.Unconfined) {
                return@async getToken()
            }.await() // 創(chuàng)建異步任務(wù)豆励,并且 阻塞執(zhí)行 await 是阻塞執(zhí)行取得結(jié)果
    
            var response = GlobalScope.async(Dispatchers.Unconfined) {
                return@async getResponse(token)
            }.await() // 創(chuàng)建異步任務(wù)夺荒,并且立即執(zhí)行
    
            setText(response)
        }
    
        Thread.sleep(20000)
    }
    

    執(zhí)行結(jié)果:

    異步請求正在執(zhí)行:getToken :0
    異步請求正在執(zhí)行:getToken :1
    異步請求正在執(zhí)行:getToken :2
    異步請求正在執(zhí)行:getToken :3
    異步請求正在執(zhí)行:getToken :4
    異步請求正在執(zhí)行:getToken :5
    異步請求正在執(zhí)行:getToken :6
    異步請求正在執(zhí)行:getToken :7
    異步請求正在執(zhí)行:getToken :8
    異步請求正在執(zhí)行:getToken :9
    異步請求正在執(zhí)行:getToken :10
    異步請求正在執(zhí)行:getResponse :ask 0
    異步請求正在執(zhí)行:getResponse :ask 1
    異步請求正在執(zhí)行:getResponse :ask 2
    異步請求正在執(zhí)行:getResponse :ask 3
    異步請求正在執(zhí)行:getResponse :ask 4
    異步請求正在執(zhí)行:getResponse :ask 5
    異步請求正在執(zhí)行:getResponse :ask 6
    異步請求正在執(zhí)行:getResponse :ask 7
    異步請求正在執(zhí)行:getResponse :ask 8
    異步請求正在執(zhí)行:getResponse :ask 9
    異步請求正在執(zhí)行:getResponse :ask 10
    setText 執(zhí)行,時間:  1578904290520
    
  • 當前正在執(zhí)行一項異步任務(wù)良蒸,但是你突然不想要它執(zhí)行了技扼,隨時可以取消

    fun main() {
        // 協(xié)程任務(wù)
        val job = GlobalScope.launch(Dispatchers.IO) {
            for (i in 0..100){// 每次掛起100MS,100次也就是10秒
                println("協(xié)程正在執(zhí)行 $i")
                delay(100)
            }
        }
    
        // 但是我在1秒之后就取消協(xié)程
        Thread.sleep(1000)
        job?.cancel()
        println( "btn_right 結(jié)束協(xié)程")
    }
    

    執(zhí)行結(jié)果(本該執(zhí)行100輪的打印嫩痰,只持續(xù)了10輪):

    協(xié)程正在執(zhí)行 0
    協(xié)程正在執(zhí)行 1
    協(xié)程正在執(zhí)行 2
    協(xié)程正在執(zhí)行 3
    協(xié)程正在執(zhí)行 4
    協(xié)程正在執(zhí)行 5
    協(xié)程正在執(zhí)行 6
    協(xié)程正在執(zhí)行 7
    協(xié)程正在執(zhí)行 8
    協(xié)程正在執(zhí)行 9
    btn_right 結(jié)束協(xié)程
    
    Process finished with exit code 0
    
  • 如果你想讓一個任務(wù)最多執(zhí)行3秒剿吻,超過3秒則自動取消

    import kotlinx.coroutines.*
    
    
    fun main() = runBlocking {
        println("限時任務(wù)中結(jié)果是:" + getResFromTimeoutTask())
    }
    
    suspend fun getResFromTimeoutTask(): String? {
        // 忘了,它會保證內(nèi)部的協(xié)程代碼都執(zhí)行完畢串纺,所以不能這么寫
        return withTimeoutOrNull(1300) {
            for (i in 0..10) {
                println("I'm sleeping $i ...")
                delay(500)
            }
            "執(zhí)行結(jié)束"
        }
    }
    

    執(zhí)行結(jié)果

    I'm sleeping 0 ...
    I'm sleeping 1 ...
    I'm sleeping 2 ...
    限時任務(wù)中結(jié)果是:null
    
    Process finished with exit code 0
    
總結(jié)

協(xié)程作為kotlin 區(qū)別于java的新概念丽旅,它的出現(xiàn)是為了解決java不好解決的問題椰棘,比如層層回調(diào)導(dǎo)致代碼臃腫,比如 異步任務(wù)執(zhí)行流程不好操控等魔招。本章節(jié)篇幅有限晰搀,無法展開說明,但是對于新手而言办斑,看完本章應(yīng)該能對協(xié)程的作用有一個大概的認知外恕。本人也是初步研究,后續(xù)有更深入的了解之后乡翅,再進行專文講解吧鳞疲。


操作符重載

概念

說人話,像是一元操作符 ++自加蠕蚜,二元操作符 +相加 尚洽,默認只支持數(shù)字類型,比如Int. 但是通過操作符的重載靶累,我們可以讓任意類 都能 ++自加腺毫,且返回一個想要的對象。操作符執(zhí)行的邏輯挣柬,完全看我們?nèi)绾稳ピO(shè)計潮酒。

分類

按元素級別

  • 一元

    表達式 對應(yīng)函數(shù)
    +a a.unaryPlus()
    -a a.unaryMinus()
    !a a.not()
    a++ a.inc()
    a-- a.dec()
  • 二元

    表達式 對應(yīng)函數(shù)
    a+b a.plus(b)
    a-b a.minus(b)
    a*b a.times(b)
    a/b a.div(b)
    a%b a.rem(b)
    a..b a.range(b)
    a in b b.contains(a)
    a !in b !b.contains(a)
    a[i] a.get(i)
    a[i,j] a.get(i,j)
    a[i_1,...,i_n] a.get(i_1,...,i_n)
    a[i]=b a.set(i,b)
    a[i,j]=b a.set(i,j,b)
    a[i_1,...,i_n]=b a.set(i_1,...,i_j,b)
    a() a.invoke()
    a(i) a.invoke(i)
    a(i,j) a.invoke(i,j)
    a(i_1,...,i_n) a.invoke(i_1,...,i_n)
    a+=b a.plusAssign(b)
    a-=b a.minusAssign(b)
    a*=b a.timesAssign(b)
    a/=b a.divAssign(b)
    a%=b a.modAssign(b)
    a > b a.compareTo(b)>0
    a < b a.compareTo(b)<0
    a>=b a.compareTo(b)>=0
    a<=b a.compareTo(b)<=0

按實現(xiàn)方式

  • 成員函數(shù)

  • 擴展函數(shù)

栗子

看到上面的一大堆,肯定有點懵邪蛔,看個例子解決疑問急黎。上面我用兩種維度來對操作符重載進行了分類,那么侧到,先試試:成員函數(shù)的方式來重載一個一元操作符

class A(i: Int, j: Int) {
    var i: Int = i
    var j: Int = j
    /**
     * 重載++操作
     */
    operator fun inc(): A {
        return A(i++, j++)
    }
    override fun toString(): String {
        return "[i=$i , j=$j]"
    }
}

如上代碼勃教,注意看:

 operator fun inc(): A {
        return A(i++, j++)
 }

Kotlin的操作符重載和 c++,dart語言內(nèi)的操作符重載寫法完全不同匠抗,它不再是直接把操作符放到了 重寫的過程中故源,而是每一種支持重載的操作符都有一個對應(yīng)的 函數(shù)名

正如:上表格中的 a++ 操作符,對應(yīng)的函數(shù)就是 a.inc()

調(diào)用的時候:

fun main() {
    var a = A(1, 2)
    println("a:$a")
    println("a++:${a++}")
}

打印結(jié)果:

a:[i=1 , j=2]
a++:[i=2 , j=3]

再看一個二元運算符重載的栗子汞贸,這次我們不用成員函數(shù)心软,而是用擴展函數(shù):

class A(i: Int, j: Int) {
    var i: Int = i
    var j: Int = j
    override fun toString(): String {
        return "[i=$i , j=$j]"
    }
}
/**
* 重載A類的 x+y 操作
*/
operator fun A.plus(a: A): A {
    return A(this.i + a.i, this.j + a.j)
}
fun main() {
    val x = A(1,1)
    val y = A(2,2)
    println(x+y)
}

這里演示的是 A類的 x+y 操作符重載。細節(jié)應(yīng)該不用多說著蛙。

打印結(jié)果:

[i=3 , j=3]

再來一個較為復(fù)雜的栗子, 重載 a[i]

/**
 * 比如,B類中有一個成員耳贬,list踏堡,我想重載操作符,直接取到list中的元素
 */
class B {
    val list: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
}
//a[i]
operator fun B.get(i: Int): Int {
    return list[i]
}
fun main() {
    val b = B()
    println("${b[2]}")
}

打印結(jié)果:

3

最后一個栗子:a > b咒劲,對應(yīng)函數(shù)為:a.compare(b)

/**
 * 學(xué)生class
 */
data class Student(val math: Int = 0, val chinese: Int = 0, val english: Int = 0)

fun Student.toString():String{
    return "[math:${math} chinese:${chinese} english:${english}]"
}
fun Student.totalScore(): Int {
    return math + chinese + english
}


/**
 * 比如顷蟆,我們要直接比較兩個學(xué)生的總分
 */
operator fun Student.compareTo(s: Student): Int {
    return this.totalScore() - s.totalScore()//比較2個學(xué)生的總分
}

fun main() {
    val s1 = Student(math = 50, chinese = 90, english = 100)
    val s2 = Student(math = 80, chinese = 70, english = 60)

    println("s1:${s1}")
    println("s2:${s2}")
    //比如存在這兩個學(xué)生诫隅,我要知道他們的總分誰高誰低
    println("學(xué)生s1,s2的總分:${if(s1 > s2) "s1比較高" else "s2比較高" }")

}

打印結(jié)果:

s1:Student(math=50, chinese=90, english=100)
s2:Student(math=80, chinese=70, english=60)
學(xué)生s1帐偎,s2的總分:s1比較高

總結(jié)

通過以上幾個栗子逐纬,應(yīng)該能看出,kotlin的操作符重載削樊,編碼和使用都十分簡單豁生。重載之前需要確定兩件事

  • 根據(jù)業(yè)務(wù)需求,確定要重載的是哪一個操作符漫贞,雖然操作符的最終執(zhí)行邏輯完全由我們自定義甸箱,但是我們重載操作符的目的是為了 讓使用者更簡單的理解業(yè)務(wù)代碼,所以迅脐,要選擇原本意義更加符合業(yè)務(wù)需求的操作符芍殖。Kotlin支持的操作符在上述表格中都列舉出來了,支持大部分一元二元操作符谴蔑,但是二元中不支持===的重載豌骏,不支持三元操作符bool?a:b這種 。
  • 確定重載函數(shù)的 入?yún)㈩愋?/strong>隐锭,個數(shù)窃躲,以及返回值類型,并且編寫操作符的執(zhí)行邏輯成榜。

潛規(guī)則

從Java轉(zhuǎn)到kotlin框舔,基本上都會存在java代碼與kotlin共存的問題。而且為了快速轉(zhuǎn)型赎婚,可能會直接把java類轉(zhuǎn)成kotlin類刘绣,而這個過程中,涉及到j(luò)ava和kotlin的交互挣输,往往會磕磕碰碰纬凤,以下總結(jié)了一部分 java kotlin交互方面的問題.

Kotlin文件和類不存在一對一關(guān)系

kotlin的文件,可以和類名一致撩嚼,也可以不一致停士。這種特性,和c++有點像完丽,畢竟c++的.h 和 .cpp文件是分開的恋技。只要最終編譯的時候?qū)Φ纳希募鋵崯o所謂的逻族。Java中蜻底,一個類文件的類名和文件名不一致,如果是public類聘鳞,就會報異常薄辅。

在kotlin中要拂,可以寫成一致,如:

不一致:

image.png

這樣做的意義在于:

如果有很多個行數(shù)很短的類:在java中可能要占用大量的文件個數(shù)(Java中可以用內(nèi)部類的形式解決)站楚,kotlin中則可以把這些類都放到同一個kt文件中脱惰,不用內(nèi)部類也能解決。


共生體

Java中的靜態(tài) static關(guān)鍵字窿春,在kotlin中不復(fù)存在拉一,作為替換,Kotlin提出了共生體的概念谁尸。如果是kt文件去調(diào)用kt類的“靜態(tài)”方法(不依賴對象)舅踪,則要求后者的類結(jié)構(gòu)中增加一個 companion object 成員變量。并且可以在 成員中寫上 你想要定義的"靜態(tài)"成員變量和成員方法

class Test001(_name: String) : Person(_name) {
    companion object {
        const val s: String = ""
        const val s2: String = ""

        fun t1(){

        }
    }
}

fun main(){
    Test001.s
    Test001.t1()
}

注:每一個kotlin類中良蛮,只能有一個共生體對象.

但是在java調(diào)用kt的"靜態(tài)"成員方法時抽碌,必須帶上共生體,但是决瞳,訪問"靜態(tài)"成員變量货徙,則不能帶:

public static void main(String[] args) {
        Test001.Companion.t1();//Java訪問kt的t1()共生體方法,必須帶上Companion
        String s2 = Test001.s;// 而訪問共生體成員變量皮胡,不能帶Companion
 }

好糾結(jié)痴颊。為什么要這么設(shè)計。算了屡贺。查了一下kt反編譯之后的Java源碼:

image.png

共生體變成了Java類中的靜態(tài)內(nèi)部類蠢棱,包含t1()方法。而s甩栈,s2 則是普通的靜態(tài)變量泻仙。


修飾符

修飾符指的是 類 和 成員變量,成員方法 前面的 權(quán)限訪問關(guān)鍵字量没。原 Java擁有 private ,protected玉转,default ,public ,訪問權(quán)限分別為: 本類內(nèi)部,同包名或子類殴蹄,同包名究抓,全局。

然而袭灯,kotlin新增了一個概念刺下,internal ,表示稽荧,相同Module內(nèi)可訪問橘茉,跨Module則不行。

并且,java和kotlin的 private ,protected捺癞,default ,public 的訪問權(quán)限還有區(qū)別,但是我這里就不詳述了构挤,因為我覺得意義不大髓介。能不能訪問,寫代碼的時候編譯器會告訴你筋现,當場警告你唐础,你就會修改代碼。如果有問題矾飞∫慌颍可以把kotlin Decompile成Java代碼自己去對比試試。如有需要洒沦,后期再說吧豹绪。


空指針問題

通常要快速的將 舊java代碼轉(zhuǎn)化成kotlin代碼,是拷貝java代碼粘貼到kotlin文件內(nèi)申眼,讓as自動轉(zhuǎn)化瞒津,但是這種方式,容易造成很多空指針問題括尸,有一些是很直白的報了編譯錯誤巷蚪,而有一些則是隱藏起來,等到程序運行時才會報錯濒翻。直接報錯的就不提了屁柏,下面演示隱藏的空指針問題:

Kotlin類:

class Student(name:String) {
    var name: String = name

    fun showName(tag: String) {
        println("$tag : $name")
    }
}

Java調(diào)用kt:

public class Main {
    public static void main(String[] args) {
        Student s = new Student("zhou");
        s.showName(null);
    }
}

此時,如果運行main函數(shù)有送,就會報出:

image.png

告訴我們參數(shù)tag不可為null淌喻。但是奇怪的是,在java代碼中娶眷,居然不會報編譯錯誤似嗤。賊特么詭異。

解決方案:

在方法參數(shù)后面加上問號届宠,變成這樣:

image.png

沒有基本數(shù)據(jù)類型

Kotlin之中沒有基本數(shù)據(jù)類型烁落,它只有:
Int,Short豌注,Long伤塌,F(xiàn)loat,Double轧铁,Byte 每聪,Char,Boolean 這樣的包裝類型。
為什么沒有药薯?沒有必要去糾結(jié)绑洛,但是只提供包裝類型有一個好處,那就是 方便擴展函數(shù)的定義童本。
我們可以很輕松地對 Int真屯,類型去擴展函數(shù)。
比如: Kotlin自帶了很多擴展函數(shù):

image.png

這是系統(tǒng)定的穷娱,我們也可以自己來定義:

fun Int.plus100(): Int {//自定義擴展
    return this + 100
}
fun main() {
    val a: Int = 20
    println("${a.plus100()}")
}

繼承

在用kt重構(gòu)部分模塊的過程中绑蔫,我發(fā)現(xiàn)頻繁出現(xiàn)下面的問題:

Kotlin基類:

abstract class Person(name: String) {
    var name: String? = name
}

Java子類:

image.png

由于我是從基礎(chǔ)類開始重構(gòu),所以泵额,在原先的Java代碼中頻繁出現(xiàn)了類似這種 訪問權(quán)限不足的問題配深。一個一個去改成setName函數(shù),工作量巨大嫁盲。后來找到一個辦法:

在kotin中加入 @JvmField 變成這樣:

abstract class Person(name: String) {
    @JvmField
    var name: String? = name
}

@JvmField可以讓 kotlin的成員屬性變成公有的篓叶,kt轉(zhuǎn)化成java時,會是如下這樣:

public abstract class Person {
   @JvmField
   @Nullable
   public String name;

   public Person(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }
}

兼容原先的Java代碼亡资。不用大面積修改了澜共。


默認支持可選命名參數(shù)

了解高級語言語法的同學(xué)肯定知道 可選命名參數(shù)可選位置參數(shù),經(jīng)測試: Kotlin的任何方法(包括構(gòu)造方法和普通和方法)锥腻,可以這么寫:

fun test001(s: String, s1: String) {
    println("$s -  $s1")
}

fun main() {
    test001(s = "1111", s1 = "2222") //臥槽嗦董,Kotlin默認支持 可選命名參數(shù)
}

這種特性可以很好的避免了Java中出現(xiàn)的一個方法包含N個參數(shù) 把人眼睛看花的情況:

private void test(String s1, String s2, String s3, String s5, String s6, String s7, String s8, String s9, String s10, String s11, String s12) {
        //...
    }

比如如上面所示,一個方法瘦黑,有12個String參數(shù)京革,看多了會罵娘,誰特么寫的幸斥。然而匹摇,用kotlin:

fun test(s1: String, s2: String, s3: String, s4: String, s5: String, s6: String, s7: String, s8: String, s9: String, s10: String, s11: String, s12: String) {}
fun main() {
    test(s1 = "",s2 = "",s3 = "",s4 = "",s5 = "",s6 = "",s7 = "",s8 = "",s9 = "",s10 = "",s11 = "",s12 = "")
}

直覺上這種語法,融入了 建造者設(shè)計模式甲葬。讓同一個函數(shù)的多個參數(shù)不再混亂廊勃。當然如果你懷舊的話,你也可以用原始方法经窖,12個string依次擺下去坡垫。反正我不會這么做。


類画侣,成員方法 默認封閉

和Java相反冰悠,kotlin給類,成員方法 都采用了默認封閉原則配乱。具體體現(xiàn)為:類溉卓,默認不可繼承皮迟,成員方法默認不可重寫(繼承時)。如果要繼承類桑寨,或者重寫父類方法伏尼,必須在父類中手動加入 open 關(guān)鍵字,子類中重寫方法必須加上override關(guān)鍵字 :

kotlin父類:

open class Student(name:String) {
    var name: String = name

    open fun showName(tag: String?) {
        println("$tag : $name")
    }
}

kotlin子類:

class StudentExt(name: String) : Student(name) {
    override fun showName(tag: String?) {
        super.showName(tag)
        println("xxxxx")
    }
}

Kotlin中方法和函數(shù)的區(qū)別

函數(shù)尉尾,是c/c++的詞匯烦粒,而方法,則是Java里面〈蓿現(xiàn)在kotlin中同時存在了方法和函數(shù),那么區(qū)別在哪里兽掰?

通常我們?nèi)藶椋涸贙otlin類內(nèi)部芭碍,稱為成員方法。而在類外部定義的孽尽,則成為全局函數(shù)(這里就不用去討論kotlin變成java之后長什么樣)窖壕。

應(yīng)用到具體場景,一句話解釋清楚:

A.kt 中有一個A類杉女,它有a()成員方法瞻讽。 同時我們可以在 B.kt中給A類擴展一個函數(shù)。創(chuàng)建一個A類對象之后熏挎,我們既可以調(diào)用a()成員方法速勇,又可以調(diào)用它的擴展函數(shù)。

A.kt

class A {
    fun a() {}
}

B.kt

fun A.foo(){}// 擴展A的一個函數(shù)

fun main() {
    val a = A()//創(chuàng)建對象
    a.a() //調(diào)用成員方法
    a.foo() //調(diào)用擴展函數(shù)
}

結(jié)語

Java轉(zhuǎn)kotlin坎拐,給我的感覺就是:

  1. kotlin對于一個function內(nèi)部的管理更加有條理烦磁,它引入了 scope 作用域的概念,利用基于lambda表達式的高階函數(shù)哼勇,把function內(nèi)部的代碼塊管理起來都伪,讓代碼可讀性更高
  2. kotlin的代碼行數(shù)會大大減少,因為kotlin設(shè)計了很多頂層函數(shù)积担,高階函數(shù)陨晶,使用大量的鏈式調(diào)用,把可以占用行數(shù)的代碼都濃縮在一行帝璧。這樣做的結(jié)果是先誉,一行代碼的信息量大大增加,對于新手是噩夢聋溜,但是對于kotlin熟手谆膳,會感覺很美妙。
  3. 關(guān)于協(xié)程撮躁,本文只做了最簡單的管中窺豹描述漱病,未曾詳細說到的東西還有很多买雾。但是可以肯定一點,協(xié)程的出現(xiàn)杨帽,顛覆了 android開發(fā)的異步編程思維漓穿,原本很多不敢想的,原本很多java實現(xiàn)起來要繞很多路的注盈,在kotlin上都可以很優(yōu)雅地實現(xiàn)晃危。

參考資料

Kotlin in Action 下載pdf書籍(https://www.7down.com/soft/209822.html
菜鳥教程 https://www.runoob.com/kotlin/kotlin-tutorial.html
kotlin中文站 https://www.kotlincn.net

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌义郑,老刑警劉巖婶芭,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門偿乖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哲嘲,你說我怎么就攤上這事贪薪。” “怎么了眠副?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵画切,是天一觀的道長。 經(jīng)常有香客問我囱怕,道長槽唾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任光涂,我火速辦了婚禮庞萍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忘闻。我一直安慰自己钝计,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布齐佳。 她就那樣靜靜地躺著私恬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪炼吴。 梳的紋絲不亂的頭發(fā)上本鸣,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音硅蹦,去河邊找鬼荣德。 笑死闷煤,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的涮瞻。 我是一名探鬼主播鲤拿,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼署咽!你這毒婦竟也來了近顷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤宁否,失蹤者是張志新(化名)和其女友劉穎窒升,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慕匠,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡异剥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了絮重。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡歹苦,死狀恐怖青伤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情殴瘦,我是刑警寧澤狠角,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站蚪腋,受9級特大地震影響丰歌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屉凯,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一立帖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悠砚,春花似錦晓勇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枢泰,卻和暖如春描融,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背衡蚂。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工窿克, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留骏庸,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓让歼,卻偏偏與公主長得像敞恋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谋右,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容