kotlin相關(guān)

一、lateinit

變量的關(guān)鍵字,可以不用在定義變量的時(shí)候就設(shè)置初始值

二、原有項(xiàng)目一些涉及到apt的第三方庫(kù),改為kotlin后,報(bào)錯(cuò),resource中沒(méi)有相關(guān)類

使用到apt相關(guān)的第三方,比如arouter,要使用kapt,但是如果你的項(xiàng)目用到了很多第三方,并且有些第三方不支持kapt的話就不行,比如lombok。有一種很土的辦法就是把kapt和java annotation配置分成兩個(gè)目錄,我沒(méi)試過(guò)感覺(jué)有點(diǎn)惡心。
但是如果支持kapt可以這改造一樣就能用:

1、apply plugin: 'kotlin-kapt'

2、有kotlin的代碼,javaCompileOptions改為kapt的

defaultConfig{
          ...
//        javaCompileOptions {
//            annotationProcessorOptions {
//                arguments = [ moduleName : project.getName() ]
//            }
//        }
        kapt {
            arguments {
                arg("moduleName", project.getName())
            }
        }
}

3、有kotlin的代碼,需要依賴配置修改annotationProcessor改為kapt

compile 'com.alibaba:arouter-api:1.3.1'
//    annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
    kapt 'com.alibaba:arouter-compiler:1.1.4'

三、let、with、run和apply

對(duì)象?.let{} 方便不為空的時(shí)候用來(lái)使用這個(gè)對(duì)象,等同于省去if(null != 對(duì)象){}的判斷;
with(對(duì)象){} 方便一些配置信息,比如變量賦值,設(shè)置是否可以顯示,設(shè)置點(diǎn)擊事件等等,用來(lái)代替builder的鏈?zhǔn)秸{(diào)用,這對(duì)安卓開(kāi)發(fā)中操作控件極其好用,因?yàn)榭丶](méi)有builder來(lái)讓你鏈?zhǔn)?
或者對(duì)一個(gè)對(duì)象連續(xù)多次操作后返回任意東西(lambda最后一行代碼返回值就是整個(gè)with返回值)都可以用with來(lái)簡(jiǎn)化代碼。

with(bt) {
            this.visibility = View.VISIBLE
            this.text = "填充按鈕文字"
            this.onClick { bt.handleKeyboard() }
        }

對(duì)象.run 和with用法一樣,只不過(guò)with是傳對(duì)象進(jìn)去,run是由對(duì)象.調(diào)用,返回值也是lambda最后一行代碼。
對(duì)象.apply 和run用法一樣,不過(guò)返回值不再是最后一行代碼了,而是返回調(diào)用對(duì)象本身。
with、run、apply都非常相似,僅有一點(diǎn)小區(qū)別,使用上靈活選擇即可。

四、標(biāo)簽 @

@標(biāo)簽 可以理解成標(biāo)記一下來(lái)源
對(duì)于嵌套for循環(huán)來(lái)說(shuō),可以指定跳出哪一層循環(huán),比java好用,比如:

//用標(biāo)簽來(lái)指定需要跳出哪個(gè)循環(huán),比java中好用
//    firstLoop@ for (i in 1..10) {
//        println("第一層循環(huán)i=${i}")
//        secondLoop@ for (j in 1..10) {
//            println("第二層循環(huán)j = ${j}")
//            if (j > 6) break@firstLoop
//            for (x in 1..6) {
//                if (x < 2) break@secondLoop
//            }
//        }
//    }

但是對(duì)于嵌套的lamda表達(dá)式foreach來(lái)說(shuō),用return+標(biāo)簽并不是跳出標(biāo)簽的foreach循環(huán),debug了一下,發(fā)現(xiàn)是continue,例子:

fun foo() {
    ints.forEach {
        if (it == 2) {
            println("滿足條件,直接下一次循環(huán)")
            return@forEach
        }
        println(it)
    }
    println("------foo")
}

打印結(jié)果是:
1
滿足條件,直接下一次循環(huán)
3
------foo

如果return不帶標(biāo)簽,則是直接結(jié)束方法,例子:

fun foo() {
    ints.forEach {
        if (it == 2) {
            println("滿足條件,直接下一次循環(huán)")
            return
        }
        println(it)
    }
    println("------foo")
}

打印結(jié)果是:
1
滿足條件,直接下一次循環(huán)

五、有時(shí)候用print打印輸出的時(shí)候,控制臺(tái)會(huì)打印出一串“kotlin.Unit”

研究了一下和print中打印的內(nèi)容有關(guān),如果打印的是一個(gè)有返回值的方法,則輸出返回值,如果打印的是一個(gè)沒(méi)有返回值的方法,就會(huì)打印出一串“kotlin.Unit”,而不是什么都不打印,為什么呢?因?yàn)閜rint調(diào)用Unit的toString方法, Unit的toString方法內(nèi)容:

public object Unit {
    override fun toString() = "kotlin.Unit"
}

六、final和open

類默認(rèn)是final,如果需要被繼承,需要加open關(guān)鍵字
fun聲明的函數(shù)默認(rèn)是final,如果需要被重寫,需要加open,子類重寫是用override關(guān)鍵字
為什么默認(rèn)是final?因?yàn)閗otlin這么設(shè)計(jì)就是為了不重蹈java覆轍。java中對(duì)final是不強(qiáng)制的,這其實(shí)是非常不安全的。java不強(qiáng)制,開(kāi)發(fā)者就基本不會(huì)主動(dòng)加final關(guān)鍵字,即使這個(gè)類一個(gè)子類都沒(méi),在項(xiàng)目越來(lái)越大之后,這種不規(guī)范的寫法就變得很危險(xiǎn),你無(wú)法知道別人會(huì)不會(huì)去繼承這個(gè)類從而導(dǎo)致一些不可控的錯(cuò)誤。

七、關(guān)于kotlin中方法和變量的override

方法:
Kotlin的繼承和實(shí)現(xiàn)中如果父類和接口有重復(fù)方法,使用super范型去選擇性地調(diào)用父類的實(shí)現(xiàn):
class C() : A() , B{
override fun f() {
super<A>.f()//調(diào)用 A.f()
super<B>.f()//調(diào)用 B.f()
}
}
和java區(qū)別比較大,java如果繼承的類和實(shí)現(xiàn)的接口中有相同方法,接口需要實(shí)現(xiàn)的方法默認(rèn)會(huì)被父類實(shí)現(xiàn),子類可以繼續(xù)重寫父類這個(gè)方法;而kotlin一定需要子類去實(shí)現(xiàn)接口的方法。


image.png

變量:
因?yàn)閗otlin的繼承不允許子類有和父類一樣的變量名。。。除非父類里面變量是private或者子類override這個(gè)變量。。。


image.png

image.png

image.png

屬性的繼承這里有一個(gè)特別要注意的點(diǎn),否則一不小心就空指針:



IDE報(bào)的是:Accessing non-final property name in constructor
不繼承就沒(méi)事


八、kotlin中的接口與java中的接口

Kotlin 接口與 Java 8 類似,使用 interface 關(guān)鍵字定義接口,允許方法有默認(rèn)實(shí)現(xiàn)接口中的屬性只能是抽象的,不允許初始化值,接口不會(huì)保存屬性值,實(shí)現(xiàn)接口時(shí),必須重寫屬性,,這和java中不同,java中接口中定義的屬性都是常量

九、kotlin中的擴(kuò)展

Kotlin中可以很方便的對(duì)一個(gè)類的屬性或方法進(jìn)行擴(kuò)展,不用像java一樣使用繼承或者裝飾模式Decorator去實(shí)現(xiàn)。擴(kuò)展不會(huì)對(duì)原有類進(jìn)行修改,注意這不是修改,只是一種靜態(tài)的行為。
在調(diào)用擴(kuò)展函數(shù)時(shí),具體被調(diào)用的的是哪一個(gè)函數(shù),由調(diào)用函數(shù)的的對(duì)象表達(dá)式來(lái)決定的,而不是動(dòng)態(tài)的類型決定的,這和java中方法的靜態(tài)分配是一樣的。
先舉一個(gè)kotlin例子:

open class C
class D : C()

//擴(kuò)展C
fun C.foo() = "c"

//擴(kuò)展D
fun D.foo() = "d"

//方法入?yún)
fun printFoo(c: C) {
    println(c.foo())
}

fun main() {
    //實(shí)際傳入D實(shí)例
    printFoo(D())
}

打印結(jié)果:c

再來(lái)一個(gè)java的例子對(duì)比一下:

public class MyTest5 {

    //方法的入?yún)㈩愋途褪庆o態(tài)類型,編譯期就可以完全確定
    public void test(Grandpa grandpa) {
        System.out.println("grandpa");
    }

    public void test(Father father) {
        System.out.println("father");
    }

    public void test(Son son) {
        System.out.println("son");
    }

    public static void main(String[] args) {
        Grandpa g1 = new Father();
        Grandpa g2 = new Son();

        MyTest5 myTest5 = new MyTest5();
        myTest5.test(g1);
        myTest5.test(g2);
    }
}
class Grandpa {
}
class Father extends Grandpa {
}
class Son extends Father {
}

打印結(jié)果:
//grandpa
//grandpa

我們從字節(jié)碼上分析一下:
main方法的Code屬性字節(jié)碼為:

0 new #7 <com/xuchun/bytecode/Father>
 3 dup
 4 invokespecial #8 <com/xuchun/bytecode/Father.<init>>
 7 astore_1
 8 new #9 <com/xuchun/bytecode/Son>
11 dup
12 invokespecial #10 <com/xuchun/bytecode/Son.<init>>
15 astore_2
16 new #11 <com/xuchun/bytecode/MyTest5>
19 dup
20 invokespecial #12 <com/xuchun/bytecode/MyTest5.<init>>
23 astore_3
24 aload_3
25 aload_1
26 invokevirtual #13 <com/xuchun/bytecode/MyTest5.test>
29 aload_3
30 aload_2
31 invokevirtual #13 <com/xuchun/bytecode/MyTest5.test>
34 return

看26、31行,invokevirtual 指令的意思是調(diào)用虛方法(存在運(yùn)行期動(dòng)態(tài)查找的過(guò)程),調(diào)用誰(shuí)的方法呢,是com/xuchun/bytecode/MyTest5.test方法,MyTest5里有三個(gè)test方法,是哪個(gè)呢,再看#13對(duì)應(yīng)的常量池里的常量信息:


image.png

可以看到方法的Name是test,參數(shù)類型是Lcom/xuchun/bytecode/Grandpa;方法返回值是void,同樣是方法的靜態(tài)分配。
靜態(tài)類型是不會(huì)變化,但是實(shí)際類型是可以再運(yùn)行期間變化的,這也是多態(tài)的體現(xiàn)。

在舉個(gè)例子加深記憶:

//擴(kuò)展函數(shù)可以被申明為open,可以被其子類覆寫,擴(kuò)展對(duì)于被擴(kuò)展函數(shù)的類是靜態(tài)的,但是對(duì)于擴(kuò)展方是虛擬的。

open class D
class D1 : D()

open class C {
    open fun D.foo() {
        println("D.foo in C")
    }

    open fun D1.foo() {
        println("D1.foo in C")
    }

    fun caller(d: D) {
        d.foo()//調(diào)用擴(kuò)展函數(shù)
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }

    override fun D1.foo() {
        println("D1.foo in D1")
    }
}

fun main() {
    C().caller(D())//D.foo in C
    C1().caller(D())//D.foo in C1
    C().caller(D1())//D.foo in C
    C1().caller(D1())//D.foo in C1
}

擴(kuò)展方法中的this:
擴(kuò)展方法中的this就是被擴(kuò)展的對(duì)象實(shí)例:

fun User.printName() {
    println(name)
}

fun User.cName(n: String) :User{
    name = n
    return this
}

fun main() {
    User("測(cè)試擴(kuò)展函數(shù)").cName("用擴(kuò)展方法重新給變量賦值").printName()
}

輸出:用擴(kuò)展方法重新給變量賦值

十、用kotlin創(chuàng)建的類或者接口,java調(diào)用時(shí)報(bào)找不到

檢查你的kotlin類或者接口中的第一行有沒(méi)有特別的符號(hào),比如:`
原因是可能你用了關(guān)鍵字作為文件夾名稱,這個(gè)文件夾中的類或者接口不會(huì)報(bào)錯(cuò),kotlin會(huì)自動(dòng)把第一行的package翻譯成kotlin不報(bào)錯(cuò)的形式,比如你用interface做了文件夾的名稱,下面的接口第一行:package com.xuchun.floatingview.`interface`,這樣的話kotlin之間互相可以使用沒(méi)問(wèn)題,java來(lái)使用就不行了。
解決方法:不用關(guān)鍵字做文件夾名稱。

十一、kotlin中單例怎么寫:

class Floater private constructor() : IFloater {
    companion object {
        val instance: Floater by lazy {
            Single.instance
        }
    }

    private object Single {
        val instance = Floater()
    }
}

kotlin調(diào)用:Floater.instance
java調(diào)用:Floater.Companion.getInstance()

十二、kotlin中的泛型

1、泛型約束:
對(duì)泛型的上界進(jìn)行約束可以讓你可以把泛型當(dāng)做它的上界類型,從而直接調(diào)用上界類型的方法,很快樂(lè)

fun <T :Number> oneHalf(value:T):Double{
    return value.toDouble()//直接就可以用Number的方法
}

所以當(dāng)你如果定義多個(gè)約束,你就可以獲得多倍快樂(lè):

fun <T> ensureTrailingPeriod(seq:T) where T:CharSequence,T:Appendable{
    //CharSequence和Appendable的方法你都可以直接用
}

快樂(lè)的代價(jià)就是要守規(guī)矩:這里表示你的seq實(shí)際傳入的類型必須要同時(shí)實(shí)現(xiàn)T:CharSequence和T:Appendable。
需要注意的是:kotlin中沒(méi)有指定上界的泛型會(huì)有一個(gè)默認(rèn)上界:Any? ,此時(shí)你的泛型參數(shù)是可空的,即使并沒(méi)有在T后面寫問(wèn)號(hào)標(biāo)記,如果此時(shí)想要設(shè)為不為空,就顯示的設(shè)定上界為Any替換掉默認(rèn)的Any?即可。

2、泛型型變:
先看一下java中泛型的型變:
型變簡(jiǎn)單理解就是類型的變化。一個(gè)類型可能有子類型,可能有父類型,在不同情況下,類型的變化是有一定規(guī)則的,不是隨心所欲的。
那么逆變與協(xié)變是什么呢?是用來(lái)描述類型變換后繼承關(guān)系,并且有一個(gè)公式可以套用:
如果??、??表示類型,??(?)表示類型轉(zhuǎn)換,≤表示繼承關(guān)系(比如,??≤??表示??是??的子類):
??(?)是逆變(contravariant)的,當(dāng)??≤??時(shí)有??(??)≤??(??)成立;
??(?)是協(xié)變(covariant)的,當(dāng)??≤??時(shí)有??(??)≤??(??)成立;
??(?)是不變(invariant)的,當(dāng)??≤??時(shí)上述兩個(gè)式子均不成立,即??(??)與??(??)相互之間沒(méi)有繼承關(guān)系。
換句話說(shuō),你如果想讓你的泛型是可以變化的,那就必須要用逆變或者協(xié)變。老師敲黑板:注意,我要變型了!
上面公式看不懂沒(méi)關(guān)系,直接看例子:
舉一個(gè)不規(guī)范但就是直觀的簡(jiǎn)單例子:

public static class 爺爺 {
}
public static class 父親 extends 爺爺 {
}
public static class 兒子 extends 父親 {
}
public static class 孫子 extends 兒子 {
}

然后定一個(gè)List變量,聲明列表容器接收兒子類型

List<兒子> list = new ArrayList<兒子>();

這樣定義,編譯和運(yùn)行都不會(huì)報(bào)錯(cuò),IDE甚至還好心提示你:Explicit type argument 兒子 can ben replaced with<>,什么意思呢,就是對(duì)你說(shuō),她很聰明的,你聲明的時(shí)候已經(jīng)明確告訴她類型了,后面實(shí)例化的時(shí)候就不用再寫一遍類型了。
既然IDE都這么提示我了,那我只能......偏不,我就寫,我還寫個(gè)不一樣的,比如:

List<兒子> list = new ArrayList<父親>();

這次IDE直接報(bào)錯(cuò)了:incompatible types:List<兒子>,ArrayList<父親>
這句英文什么意思呢,就是IDE罵人了:讓你系安全帶你不系,你xx!
不好意思翻譯錯(cuò)了,實(shí)際意思說(shuō)的是:這兩個(gè)類型是矛盾的!
我們帶入上面的公式,得到??(兒子) = ArrayList<兒子>,??(父親) = ArrayList<父親>,如果泛型是逆變,則ArrayList<兒子>是ArrayList<父親>的父類,上面的例子報(bào)錯(cuò)已經(jīng)證明了,ArrayList<兒子>并不是ArrayList<父親>的父類型,同樣泛型也不是協(xié)變,實(shí)際上泛型沒(méi)有任何繼承關(guān)系,也就是說(shuō)泛型是不變的。
那怎么改呢?怎么申明類型才能又接收兒子又接受父親呢?這樣:

List<? super 兒子> list = new ArrayList<父親>();

這個(gè)類型不知道到底是兒子還是爸爸,所以寫成”?“(java通配符,代表任何類型),"? super 兒子"就表示這個(gè)類型可以是兒子或者是兒子的父類,那誰(shuí)是兒子的父類呢,爸爸和爺爺,所以把爺爺捉過(guò)來(lái)放進(jìn)去也沒(méi)問(wèn)題。(爺爺說(shuō):莫挨老子)
也就是說(shuō),泛型是不變的,但是我們用別的辦法實(shí)現(xiàn)了泛型的逆變。

List<? super 兒子> list = new ArrayList<爺爺>();

“? super” 就實(shí)現(xiàn)了泛型的”逆變“,

那現(xiàn)在孫子還沒(méi)用上呢,再改一下:

List<兒子> list = new ArrayList<孫子>();

果然不出所料,IDE又開(kāi)罵了:你XX。
不對(duì)啊,兒子是孫子的父類,正常情況下,是可以聲明一個(gè)父類變量給他賦值子類對(duì)象呀,比如兒子 erzi = new 孫子()。但是編譯器已經(jīng)報(bào)錯(cuò)告訴你了
List<兒子>和 ArrayList<孫子>類型是矛盾的!也就是說(shuō)兒子是孫子的父類,不代表List<兒子>就是 List<孫子>的父類,所以沒(méi)有繼承關(guān)系當(dāng)然不能類型轉(zhuǎn)換,這里又驗(yàn)證了一遍泛型是不變的。
趕緊改吧:

List<? extends 兒子> list = new ArrayList<孫子>();

不報(bào)錯(cuò)了,"? extends 兒子"就表示這個(gè)類型可以是兒子或者兒子的子類。孫子是兒子的子類,所以沒(méi)問(wèn)題。這就實(shí)現(xiàn)了泛型的”協(xié)變“。

上面的例子只做了賦值操作,在使用了協(xié)變或逆變后都可以讓賦值操作編譯正確。
但是當(dāng)你想往list里存數(shù)據(jù)時(shí),比如:

List<? extends 兒子> list = new ArrayList<孫子>();
孫子 sunzi =  new 孫子();
list.add(sunzi);

編譯會(huì)報(bào)如下錯(cuò)誤:

Error:(40, 13) java: 對(duì)于add(decorator.MainTest.孫子), 找不到合適的方法
    方法 java.util.Collection.add(capture#1, 共 ? extends decorator.MainTest.兒子)不適用
      (參數(shù)不匹配; decorator.MainTest.孫子無(wú)法轉(zhuǎn)換為capture#1, 共 ? extends decorator.MainTest.兒子)
    方法 java.util.List.add(capture#1, 共 ? extends decorator.MainTest.兒子)不適用
      (參數(shù)不匹配; decorator.MainTest.孫子無(wú)法轉(zhuǎn)換為capture#1, 共 ? extends decorator.MainTest.兒子)

意思就是不能把孫子類型存到list中。實(shí)際上這個(gè)list不能存除了null之外的任何類型,包括兒子。也就是說(shuō)List<? extends 兒子>喪失了”寫“的能力!
相對(duì)應(yīng)的:

List<? super 兒子> list = new ArrayList<父親>();
父親 fuqin =  new 父親();
 list.add(fuqin);

一樣會(huì)報(bào)上面的錯(cuò)誤,但是和? extends有點(diǎn)區(qū)別的是,這個(gè)list可以存null和兒子類型及其子類型(孫子)!
不信我們操作一下:

兒子 erzi =  new 兒子();
孫子 sunzi =  new 孫子();
list.add(erzi);
list.add(sunzi);
list.add(null);
list.forEach(System.out::println);//打印一下

打印結(jié)果:
decorator.MainTest$兒子@7ef20235
decorator.MainTest$孫子@27d6c5e0
null

奇怪了,定義的類型明明是兒子和兒子的父類,不能往里添加父親就算了,但是為啥可以往里添加兒子和兒子的子類?!

下面來(lái)探究為什么這兩個(gè)list不能完整的使用add方法,甚至不能使用add方法。
先打印下他們倆的類型:

List<? super 兒子> list = new ArrayList<父親>();
List<? extends 兒子> list2 = new ArrayList<孫子>();
System.out.println("list的類型是:" + list.getClass());
System.out.println("list2的類型是:" + list2.getClass());

打印結(jié)果:
list的類型是:class java.util.ArrayList
list2的類型是:class java.util.ArrayList

他兩都是ArrayList類型!<父親>,<孫子>這些都沒(méi)了,那我還在上面費(fèi)勁吧啦的定義類型干什么!
那我們指定的類型去哪了呢?會(huì)不會(huì)在List內(nèi)部記錄了這個(gè)類型。

Class c = list.getClass();
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
       System.out.println("屬性名= " + f.getName() + "  屬性類型 = " + f.getType().getName());
}

打印結(jié)果:
屬性名= serialVersionUID 屬性類型 = long
屬性名= DEFAULT_CAPACITY 屬性類型 = int
屬性名= EMPTY_ELEMENTDATA 屬性類型 = [Ljava.lang.Object;
屬性名= DEFAULTCAPACITY_EMPTY_ELEMENTDATA 屬性類型 = [Ljava.lang.Object;
屬性名= elementData 屬性類型 = [Ljava.lang.Object;
屬性名= size 屬性類型 = int
屬性名= MAX_ARRAY_SIZE 屬性類型 = int

怎么肥事,elementData類型都是Object。也就是說(shuō)這個(gè)list實(shí)際是可以存任意類型的!換句話說(shuō)泛型的類型被抹去了,變成了Object(這也是為什么泛型不能是基本類型的原因,想存基本類型也只能用它的包裝類)。雖然編譯期在我們寫代碼的時(shí)候會(huì)檢查提示錯(cuò)誤,但是我們可以用反射繞過(guò)檢查試一下:

 List<? extends 兒子> list2 = new ArrayList<孫子>();
 孫子 sunzi = new 孫子();
//        list2.add(sunzi);//會(huì)報(bào)錯(cuò)
 list2.getClass().getMethod("add",Object.class).invoke(list2,sunzi);
 System.out.println(list2.get(0));

打印結(jié)果:
decorator.MainTest$孫子@5e2de80c

說(shuō)明確實(shí)可以存進(jìn)去,并且,還可以突破? extends 兒子這個(gè)限制,把兒子的父類傳進(jìn)去都可以:

父親 fuqin =  new 父親();
 list2.getClass().getMethod("add",Object.class).invoke(list2,fuqin);
 System.out.println(list2.get(1));

打印結(jié)果:
decorator.MainTest$父親@5e2de80c

這不僅能驗(yàn)證運(yùn)行期間可以存任意類型,而且還能說(shuō)明,編譯器對(duì)我們編寫的代碼,是先檢查我們定義的泛型的類型,然后再去編譯成可以存任意類型的,也就是對(duì)泛型的類型,編譯器是先“檢查”后“編譯并抹去類型”。
看一下編譯后生成的字節(jié)碼文件局部變量表,也沒(méi)有任何指定的泛型信息。

這里其實(shí)是java語(yǔ)言的一個(gè)特性,那就是java中的泛型是個(gè)偽泛型,編譯后泛型信息就沒(méi)了,只剩下了原始類型(原始類型是什么一會(huì)說(shuō)),這個(gè)過(guò)程叫做”類型擦除”。
為什么要弄這個(gè)類型擦除呢,因?yàn)閖ava5之前是沒(méi)有泛型的,也就是說(shuō)list的add可以放任何類型,那么java5之后為了既能向下兼容,又要解決類型安全和類型自動(dòng)轉(zhuǎn)換的問(wèn)題,于是就設(shè)計(jì)成了類型擦除。
可是類型都被擦除了,我們調(diào)用add方法編譯器還會(huì)給我們報(bào)錯(cuò)呢,原因上面我們已經(jīng)驗(yàn)證過(guò)了:編譯器是先檢查后編譯擦除的。這其實(shí)也是泛型出現(xiàn)的一個(gè)原因:把對(duì)類型的檢查提前編譯之前,來(lái)確保類型安全,要知道泛型沒(méi)出現(xiàn)之前,list的add可以放任何類型,是非常不安全的。
kotlin和java一樣,也有類型擦除,所以你在運(yùn)行時(shí)是沒(méi)法檢查你的泛型的:

 if(value instanceof List<String>)//java寫法:報(bào)錯(cuò)
if(value is List<String>)//kotlin寫法:報(bào)錯(cuò)

正確寫法就是java用不指定泛型實(shí)際類型或者使用通配符,kotlin用投影語(yǔ)法星號(hào):

if(value instanceof List)//java寫法1
if(value instanceof List<?>)//java寫法2
if(value is List<*>)//kotlin寫法

到這里我們就知道了,設(shè)置的泛型類型其實(shí)并不會(huì)被帶到運(yùn)行期,只是為了編譯前的一個(gè)安全檢查,所以add方法為什么會(huì)報(bào)錯(cuò)實(shí)際和編譯器的檢查規(guī)則有關(guān):
1、當(dāng)定義為L(zhǎng)ist<? extends XXX>時(shí),也就是對(duì)加入的元素進(jìn)行了上限限制,表示可以加入的元素是XXX和XXX的子類,此時(shí)編譯器是不知道這個(gè)類型具體是哪一個(gè)的,編譯器是很怕死的,于是為了類型安全和類型自動(dòng)轉(zhuǎn)換,編譯器就禁止add除了null以外任何類型,舉個(gè)例子:Integer和Double都extends了Number,那么當(dāng)list定義為L(zhǎng)ist<? extends Number>時(shí),add(100)是禁止的,因?yàn)槟氵@個(gè)100到底是是Integer還是Double?
那可能會(huì)疑惑,add都不能用了,那肯定也沒(méi)元素能取出來(lái)了,那這個(gè)list有什么意義呢,別忘了它是可以被賦值并取出元素的:

List<? extends 兒子> list2 = new ArrayList<孫子>();
List<孫子> list3 = new ArrayList<>();
孫子 sunzi = new 孫子();
list3.add(sunzi);
list2 = list3;
System.out.println(list2.get(0) );
//打印:decorator.MainTest$孫子@60e53b93

也就是說(shuō)? extends這個(gè)限定是具有只讀特性的!
2、當(dāng)定義為L(zhǎng)ist<? super XXX>時(shí),也就是對(duì)加入的元素進(jìn)行了下限限制,此時(shí)可以加入的元素是XXX和XXX的父類,XXX的父類可能很多,鬼知道你要傳哪一個(gè),因此此時(shí)編譯器還是不知道你傳的具體類型是哪一個(gè),所以不允許add這個(gè)XXX類的父類,即使是Object這個(gè)上帝父類也不行,那么為什么允許add這個(gè)XXX類的子類呢?因?yàn)閖ava中繼承的特性,XXX類的子類可以被看做XXX類,所以可以被當(dāng)做XXX存放進(jìn)去,只要不是XXX類的父類就行,因?yàn)榫幾g器不知道你要放哪個(gè)父類,它怕死啊。

那么原始類型是什么呢?就是泛型被擦除后的類型(如果沒(méi)有限定就是Object,有限定就是限定后的第一個(gè))因?yàn)樽止?jié)碼文件是被類型擦除后的,所以我們看一下字節(jié)碼文件:

List<? super 兒子> list = new ArrayList<父親>();
List<? extends 兒子> list2 = new ArrayList<孫子>();

因?yàn)閮蓚€(gè)list我是直接定義在main方法中,所以去找一下main方法的局部泛型變量表(LocalVariableTypeTable這個(gè)表是專門保存泛型變量簽名的)看一下這兩個(gè)list的原始類型:



這里類型沒(méi)有顯示完整,但是告訴我們對(duì)應(yīng)的是51和52索引的常量,我們跳轉(zhuǎn)過(guò)去看一下:




其中“兒子”就是原始類型:

“+”表示的就是“? extends”,表示上限限定,不可寫;
“-”表示的就是“? super”,表示下限限定,可寫入其和其子類。

在原始類型這一塊,kotlin和java使用上有一些不同,因?yàn)閗otlin一開(kāi)始就被設(shè)計(jì)成是有泛型概念的,所以kotlin中泛型定義是不支持把泛型定義成不指定類型的,你必須要指定泛型類型,舉例:
java中可以不指定泛型類型,表示這個(gè)列表中可以存放任意類型:

List numberList = new ArrayList<>(); //編譯通過(guò)

而kotlin不能這么寫,必須指定泛型類型:

val numberList:MutableList = mutableListOf() //編譯報(bào)錯(cuò):One type argument expected for interface MutableList<E>

val numberList:MutableList<Number> = mutableListOf()  //正確寫法
val numberList = mutableListOf<Number>() //正確寫法

在kotlin中,消費(fèi)者(逆變)用關(guān)鍵字in,相當(dāng)于java中的super;生產(chǎn)者(協(xié)變)用關(guān)鍵字out,相當(dāng)于java中的extends。
(對(duì)于in和out兩個(gè)關(guān)鍵字,個(gè)人記憶的方式:協(xié)變是生產(chǎn)者,生產(chǎn)者是輸出生成的東西,所以是out,相反逆變是消費(fèi)者,消費(fèi)者是消費(fèi)進(jìn)來(lái)的東西,就是in。其實(shí)out和in分別也對(duì)應(yīng)著函數(shù)的返回值位置和入?yún)⑽恢茫@是編譯器強(qiáng)制限制的)
最后再?gòu)?qiáng)調(diào)一下,協(xié)變不可寫,逆變可寫。需要從泛型“讀”用協(xié)變,需要往泛型“寫”用逆變。
來(lái)對(duì)比一下java中的List<E>和kotlin中List<out E>:
從類定義上可以看出java中的List是泛型不變的,所以可讀可寫,而kotlin中的List是協(xié)變的,所以只讀,看一下類結(jié)構(gòu):
java.util.List:



確實(shí)是可讀可寫;
kotlin.collections.List:



只有讀的方法,沒(méi)有寫(add/set/remov等)的方法。
所以在kotlin中你如果想讓你的列表可以動(dòng)態(tài)增減數(shù)據(jù)就不能用List,而需要用無(wú)型變的MutableList。

3、kotlin中的泛型實(shí)化(泛型具體化reified)
泛型實(shí)化或者叫泛型具體化,是kotlin中對(duì)泛型擴(kuò)展出的一種能力,優(yōu)點(diǎn)是讓原本會(huì)報(bào)錯(cuò)的一些便捷寫法變成可能,比如:a as T,T::class.java
,直接的好處就是可以讓你的代碼寫起來(lái)更便捷。舉例:
比如請(qǐng)求網(wǎng)絡(luò),使用了Retrofit,一般都會(huì)寫一個(gè)Retrofit的單例類,對(duì)外提供一個(gè)方法,傳入某個(gè)ServiceApi的類型來(lái)生成一個(gè)serviceApi的對(duì)象。

object ServiceCreator {
    private const val BASE_URL = ""
    private val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    fun <T> create(serviceClass: Class<T>): T {
        return retrofit.create(serviceClass)
    }
}

外部在使用的時(shí)候就寫成:

ServiceCreator.create(AppService::class.java)

這樣寫不夠便捷,::class.java這么一大段實(shí)際都是為了機(jī)器更方便閱讀,對(duì)人來(lái)說(shuō)不直觀,利用泛型具體化來(lái)讓它更便捷和直觀:
再寫一個(gè)方法:(泛型具體化的語(yǔ)法:inline和reified關(guān)鍵字)

inline fun <reified T> create():T = create(T::class.java)

外部在使用的時(shí)候就寫成:

ServiceCreator.create<AppService>()

十三: Lambda

正常方法的入?yún)⒍际且粋€(gè)個(gè)變量,lambda用白話說(shuō)就是可以當(dāng)方法入?yún)⒌拇a塊。(當(dāng)然你也可以把lambda表達(dá)式賦值給一個(gè)變/常量)

kotlin中Lambda表達(dá)式的語(yǔ)法結(jié)構(gòu):{參數(shù)1:類型,參數(shù)2:類型 ->函數(shù)體}

首先是一個(gè)大括號(hào)包裹全部,內(nèi)部是參數(shù)列表,-> 符號(hào)表示參數(shù)列表結(jié)束,后面緊跟函數(shù)體,并且函數(shù)體中最后一行代碼就是這個(gè)lambda表達(dá)式的返回值。

如果定一個(gè)lambda表達(dá)式的變/常量,它的類型就是很長(zhǎng)一串:(參數(shù)1類型,參數(shù)2類型)->最后一行代碼返回值類型

這其實(shí)是kotlin中的一個(gè)概念,叫做”函數(shù)類型“,是一種特殊的類型,用于高階函數(shù),簡(jiǎn)單的理解就是這個(gè)類型聲明了一個(gè)函數(shù)的出入?yún)㈩愋头謩e是什么。
舉例:
已有一個(gè)列表:val list: List<String> = listOf<String>("Apple", "Banana", "Orange", "Pear")
需求:定義一個(gè)取長(zhǎng)度的Lambda并且用一個(gè)常量保存它:

val lengthLambda = {fruit:String -> fruit.length}//類型:(String)->Int

lengthLambda是常量名,大括號(hào)中有一個(gè)參數(shù)fruit,參數(shù)類型是String,函數(shù)體只有一段代碼,fruit.length,所以lambda的返回值就是String。
現(xiàn)在需要寫一個(gè)方法,接收一個(gè)lambda當(dāng)做入?yún)ⅲ祷匾粋€(gè)列表中長(zhǎng)度最大的值:
思路:
因?yàn)樯婕暗搅斜硌h(huán),所以我們直接用Iterable擴(kuò)展函數(shù);又因?yàn)樯婕暗奖容^,所以用于比較的那個(gè)類型一定會(huì)繼承Comparable;我們用泛型來(lái)使方法適用于更多場(chǎng)景:

fun <T,R : Comparable<R>> Iterable<T>.myTestMaxBy(selector: (T) -> R) : T?

上面是我們的方法定義,首先是fun關(guān)鍵字,表示這是一個(gè)方法;
接著<>表示這個(gè)方法是一個(gè)泛型方法,尖括號(hào)里面的內(nèi)容:T,R : Comparable<R>表示泛型有兩個(gè)類型:T和R,其中R是Comparable的子類,表示它可以用Comparable的方法,這兩個(gè)類型放在我們上面的場(chǎng)景中分別表示的就是list的泛型類型(水果名字String)和用于比較的類型(水果名字長(zhǎng)度Int);
再往后Iterable<T>.myTestMaxBy 表示myTestMaxBy這個(gè)方法是對(duì)Iterable類的擴(kuò)展;
繼續(xù)往后,方法入?yún)⑹顷P(guān)鍵,我們需要定義的是一個(gè)lambda入?yún)?,那怎么寫呢?很?jiǎn)單,按正常的參數(shù)名:參數(shù)類型 這種格式寫即可。所以參數(shù)名我們叫selector,那么參數(shù)類型是什么?它的類型按我們上面定義的lengthLambda常量可以推導(dǎo)出是:(T) ->R;
最后這個(gè)方法需要返回列表里面長(zhǎng)度最大的那個(gè)值,所以返回值類型就是列表的泛型T,允許為空T?。
(myTestMaxBy方法的入?yún)⑹且粋€(gè)函數(shù)類型,說(shuō)明它是一個(gè)高階函數(shù))
具體實(shí)現(xiàn):

fun <T, R : Comparable<R>> Iterable<T>.myTestMaxBy(selector: (T) -> R): T? {
    val iterator = iterator()
    if (!iterator.hasNext()) return null//如果集合中沒(méi)有元素,直接返回null
    var maxElement = iterator.next()
    if (!iterator.hasNext()) return maxElement//如果集合中只有一個(gè)元素。返回它
    var maxValue = selector(maxElement)
    do {
        val element = iterator.next()
        val value = selector(element)
        if (value > maxValue) {
            maxElement = element
            maxValue = value
        }
    } while (iterator.hasNext())
    return maxElement
}

其中if里面的比較就用到了Comparable方法的compareTo方法,可能有人說(shuō)沒(méi)看到compareTo呀,那是因?yàn)閏ompareTo是一個(gè)operator方法,我們?cè)谟么笥谛∮诜?hào)的時(shí)候其實(shí)就是再調(diào)用這個(gè)方法,不信你別讓R繼承Comparable,if那里就報(bào)錯(cuò)了。
最后可以把我們定義的lambda常量傳入到這個(gè)方法中:

val lambda:(String)->Int = { fruit: String -> fruit.length }
val maxLength :String?= list.myTestMaxBy(lambda)
println("列表里名字最長(zhǎng)的水果 = ${maxLength}")//Banana

這個(gè)方法實(shí)際上和kotlin自帶的集合函數(shù)式API一樣:_Collections.kt:maxBy
上面的寫法可以簡(jiǎn)化一下,因?yàn)橐婚_(kāi)始我們就說(shuō),lambda就是一種可以當(dāng)入?yún)⒌拇a塊,所以不需要定義一個(gè)常量來(lái)保存它,直接把它全部復(fù)制往方法里一傳就完事了:

val maxLength = list.myTestMaxBy({ fruit: String -> fruit.length })

此時(shí)IDE會(huì)給你彈出一個(gè)建議:Lambda argument should be moved out of parentheses,意思是Lambda參數(shù)應(yīng)該移到圓括號(hào)外面,實(shí)際上這是Kotlin中一個(gè)規(guī)定:當(dāng)Lambda參數(shù)是函數(shù)最后一個(gè)參數(shù)時(shí)候,可以把Lambda移到括號(hào)外面:

val maxLength = list.myTestMaxBy(){ fruit: String -> fruit.length }

此時(shí)括號(hào)里沒(méi)有任何參數(shù)定義,括號(hào)也可以省了。
又因?yàn)镵otlin中類型會(huì)自動(dòng)推導(dǎo),所以fruit的類型也不用寫:

val maxLength = list.myTestMaxBy { fruit -> fruit.length }

kotlin中還有一個(gè)特性:當(dāng)lambda表達(dá)式的參數(shù)列表只有一個(gè)時(shí),參數(shù)定義都不用寫,可以用關(guān)鍵字it代替,當(dāng)然這個(gè)隨便你,你要是覺(jué)得定義一些參數(shù)名更直觀,就保留好了:

val maxLength = list.myTestMaxBy { it.length }

可以嘗試自己實(shí)現(xiàn)一下集合的另一個(gè)API:map。
注意其中涉及到對(duì)集合的寫入,所以需要用到泛型逆變。逆變的原理在這篇文章第#十二。
列出幾個(gè)常用的集合函數(shù)式API:
map:把集合元素根據(jù)條件轉(zhuǎn)為另一種元素排出,和JAVA8 STREAM里的map一樣。
filter:返回符合過(guò)濾條件的元素。
any:判斷集合中是否至少存在一個(gè)元素滿足條件,返回boolean。
all:判斷集合中是否所有元素都滿足條件,返回boolean。

經(jīng)常能見(jiàn)到高階函數(shù)這樣定義:

fun SharedPreferences.edit(commit: Boolean = false, action: SharedPreferences.Editor.() -> Unit)

一、這個(gè)函數(shù)類型前面有一個(gè)“SharedPreferences.Editor.”,
1、含義和優(yōu)點(diǎn):首先,這也是函數(shù)類型定義的一種語(yǔ)法規(guī)則。
這表示把函數(shù)類型定義在了SharedPreferences.Editor這個(gè)類中,并且這個(gè)函數(shù)類型內(nèi)部會(huì)自動(dòng)擁有這個(gè)類的上下文。這是這種寫法的一個(gè)優(yōu)點(diǎn),讓你可以在lambda中通過(guò)this(可省略)直接調(diào)用這個(gè)類的所有可用方法。
(看起來(lái)有點(diǎn)像擴(kuò)展函數(shù),但是其實(shí)不是,你沒(méi)法在這個(gè)高階函數(shù)之外調(diào)用這個(gè)函數(shù)類型,因?yàn)樗冀K本身就是個(gè)特殊類型(函數(shù)類型))。
調(diào)用這個(gè)高階函數(shù):


在使用的地方看到IDE提示的this類型就是SharedPreferences.Editor類。
(此時(shí)lambda中如果用it調(diào)用可以嗎?答案是不行。后續(xù)分析會(huì)用到這個(gè)結(jié)論)


2、使用:用這種語(yǔ)法來(lái)定義函數(shù)類型聲明時(shí),高階函數(shù)內(nèi)部調(diào)用它時(shí)有兩種寫法:

fun SharedPreferences.edit(commit: Boolean = false, action: SharedPreferences.Editor.() -> Unit) {
    val editor = this.edit()
    action(editor)//這樣調(diào)用沒(méi)問(wèn)題
    editor.action()//這樣調(diào)用沒(méi)問(wèn)題
}

3、原理:可以看到上面兩種調(diào)用方式,一個(gè)有入?yún)⒁粋€(gè)沒(méi)有入?yún)?,我們定義的時(shí)候也是一個(gè)空的括號(hào),那么它到底有沒(méi)有入?yún)⒛???shí)際上是有的,看一下反編譯后的代碼:


首先看到原本函數(shù)類型的位置現(xiàn)在是一個(gè)接口類型Function1:

public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}

這個(gè)接口只有一個(gè)函數(shù),這個(gè)函數(shù)只有1個(gè)入?yún)ⅲ‵unction2表示有2個(gè)入?yún)ⅲ渌麛?shù)字同理類推),并且是個(gè)泛型接口,定義了兩個(gè)泛型P1和R,分別用在了invoke方法的入?yún)㈩愋秃头祷仡愋?。但是因?yàn)樽止?jié)碼的類型擦除機(jī)制導(dǎo)致這里是看不到具體類型(不知道類型擦除機(jī)制的,往上看第十二條)
然后兩個(gè)調(diào)用的位置實(shí)際最后都被轉(zhuǎn)換成了調(diào)用Function1的invoke方法。實(shí)際入?yún)⒕褪撬诘念惖膶?shí)例對(duì)象(返回參數(shù)是Unit)。
用大白話說(shuō)就是你定義的函數(shù)類型被Function1類型替代了,你的函數(shù)類型調(diào)用的地方被Function1的invoke方法替代了。
再反編譯調(diào)用這個(gè)高階函數(shù)的SpUtil類:


調(diào)用SharedPreferences.edit時(shí),new了一個(gè) Function1對(duì)象進(jìn)去,并且實(shí)現(xiàn)了invoke方法,invoke方法的入?yún)⒈粡?qiáng)轉(zhuǎn)成了Editor類型使用,最后返回一個(gè)Unit對(duì)象,invoke方法內(nèi)部又調(diào)用了一個(gè)final方法(橋接),這個(gè)方法接收一個(gè)Editor類型,內(nèi)部的邏輯就是我們寫在lambda中的邏輯,一模一樣。
用大白話說(shuō)就是你在lambda中寫的邏輯都被封裝成另一個(gè)方法,在invoke中被調(diào)用了。

二、在函數(shù)作用不變的前提下,如果換一種定義方式呢?
1、定義:

fun SharedPreferences.edit(commit: Boolean = false, action: (SharedPreferences.Editor) -> Unit) {
    val editor = this.edit()
    action(editor)//這樣調(diào)用沒(méi)問(wèn)題
    editor.action()//這樣調(diào)用不行
}

2、和上一種寫法的區(qū)別:
這時(shí)候的action函數(shù)類型是(SharedPreferences.Editor) -> Unit,和上面寫法的區(qū)別是沒(méi)有把這個(gè)函數(shù)類型指定在某個(gè)類中了,那說(shuō)明lambda中不可能在有這個(gè)類的上下文了,并且調(diào)用action的時(shí)候也只有傳入一個(gè)SharedPreferences.Editor類型的參數(shù)才能正確編譯了。
看一下反編譯:



和上一個(gè)寫法反編譯后的邏輯沒(méi)區(qū)別,同樣是調(diào)用Function1的invoke方法,傳入一個(gè)Editor實(shí)例。

那看一下調(diào)用這個(gè)高階函數(shù)的地方有沒(méi)有什么變化:



變化很大,原先的this已經(jīng)變成了it,雖然類型都還是SharedPreferences.Editor,但是已經(jīng)享受不到this帶來(lái)的省略寫法了,putString和putInt已然飄紅,需要用it.來(lái)調(diào)用它們。



把這個(gè)反編譯看一下:

和上一個(gè)寫法沒(méi)有本質(zhì)區(qū)別。
所以這種寫法和上一種寫法除了在你寫lambda內(nèi)部邏輯時(shí)有些區(qū)別(第一種寫法可以使用this,寫起來(lái)更方便),其他沒(méi)有區(qū)別。

三、現(xiàn)在想把第一和第二種寫法結(jié)合在一起,也就是在第二種寫法的基礎(chǔ)上,同時(shí)把這個(gè)函數(shù)類型給定義到SharedPreferences.Editor類中:



可以看到在使用action的兩個(gè)地方都報(bào)錯(cuò)了:No value passed for parameter 'p2',意思是參數(shù)2沒(méi)有傳值。
啥也不管了直接看反編譯:(先把使用action的兩個(gè)地方注釋了)


image.png

可以看到之前是Function1的入?yún)⒆兂闪薋unction2:
/** A function that takes 2 arguments. */
public interface Function2<in P1, in P2, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2): R
}

Function2這個(gè)接口的invoke方法有兩個(gè)入?yún)?,p1和p2,所以當(dāng)我們使用action(editor)時(shí)會(huì)提示我們參數(shù)2沒(méi)有傳值,那我們給傳一下參數(shù)2:



這就不用反編譯看了吧,這兩個(gè)使用action的地方肯定都會(huì)轉(zhuǎn)變成action.invoke(editor, editor);
那么在使用這個(gè)高階函數(shù)的地方,lambda中是this還是it呢?
用it:



用this:

都可以!這和第一和第二種寫法就有區(qū)別了,第一種寫法只能用this,第二種寫法只能用it。
這樣其實(shí)沒(méi)啥意義,只是為了分析寫法的區(qū)別。/笑哭

四、現(xiàn)在還是用第三種寫法,但是我不把函數(shù)類型定義到Editor類中,我給它換個(gè)家,給它定義到String中,看看會(huì)咋樣:



反編譯:



這其實(shí)可以得到一個(gè)結(jié)論:如果這個(gè)函數(shù)類型被指定到了某一個(gè)類中,那么編譯后invoke的第一個(gè)入?yún)⒍际沁@個(gè)類的實(shí)例。
注意,下面開(kāi)始好玩了:

在使用這個(gè)高階函數(shù)的地方,lambda中this和it兩種方式還可以使用嗎?可以使用的話this和it還是同一個(gè)類型嗎?
使用it:




可以看到it是Editor類型。
使用this:


可以看到this是String類型,并且Function2傳的是一個(gè)null的實(shí)例,那這樣的話lambda中的內(nèi)容是不是沒(méi)有被執(zhí)行?并不是,會(huì)被執(zhí)行,不信你打個(gè)log看一下。

明明傳進(jìn)去是(Function2)null.INSTANCE,invoke方法都被看到被覆寫,怎么就執(zhí)行了呢?我也不知道,有知道的希望評(píng)論回復(fù)我,感謝!

最后總結(jié)下,在不涉及泛型的情況下還是用第一種(也即是把函數(shù)類型指定到某一個(gè)類中)的寫法既規(guī)范又簡(jiǎn)便,使用起來(lái)更方便。

十四、密封類的作用

當(dāng)你使用when時(shí),kotlin語(yǔ)法會(huì)強(qiáng)制要求你寫else,即使你能確定這個(gè)else永遠(yuǎn)用不上,這樣不方便的同時(shí)會(huì)有一個(gè)很大的風(fēng)險(xiǎn):當(dāng)你新增了一個(gè)條件,但是忘記在when對(duì)應(yīng)的地方添加對(duì)應(yīng)條件分支,編譯器也不會(huì)提醒你,這時(shí)候你新增的條件就會(huì)走到else中,這不是我們想要的,這個(gè)問(wèn)題的本質(zhì)就是這個(gè)else,如果不用寫它就不會(huì)有這個(gè)問(wèn)題,并且我還想要編譯器可以提醒我去在when中添加對(duì)應(yīng)條件分支,這時(shí)候就可以用密封類來(lái)解決這個(gè)問(wèn)題。
當(dāng)when中傳入的是一個(gè)密封類,語(yǔ)法就允許我們不用寫else,并且當(dāng)你新增一個(gè)密封類的子類時(shí),編譯器會(huì)報(bào)錯(cuò),提醒你要在when中增加對(duì)應(yīng)的條件分支。

十五、可見(jiàn)性控制

什么叫可見(jiàn)性,舉個(gè)安卓源碼中的例子:

ActivityThread是一個(gè)public的類,但是應(yīng)用層開(kāi)發(fā)者卻訪問(wèn)不到這個(gè)類,因?yàn)橛昧丝梢?jiàn)性注解修飾@hide,表示其不作為對(duì)外Api被訪問(wèn)。
在kotlin中對(duì)應(yīng)internal關(guān)鍵字,比如在某個(gè)module中給某個(gè)類加了internal關(guān)鍵字,module中可以用這個(gè)類,但是在你的app工程就無(wú)法使用這個(gè)類了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容