Kotlin知識歸納(四) —— 接口和類

????Kotlin的類和接口與Java的類和接口存在較大區(qū)別,本次主要?dú)w納Kotlin的接口和類如何定義、繼承以及其一些具體細(xì)節(jié),同時查看其對應(yīng)的Java層實(shí)現(xiàn)。

帶默認(rèn)方法的接口

????Kotlin接口可以包含抽象方法以及非抽象方法的實(shí)現(xiàn)(類似Java 8的默認(rèn)方法)

interface MyInterface {
    //抽象方法
    fun daqi()
    //非抽象方法(即提供默認(rèn)實(shí)現(xiàn)方法)
    fun defaultMethod() {
    }
}

????接口也可以定義屬性。聲明的屬性可以是抽象的,也可以是提供具體訪問器實(shí)現(xiàn)的(即不算抽象的)。

interface MyInterface {
    //抽象屬性
    var length:Int
    //提供訪問器的屬性
    val name:String
        get() = ""

    //抽象方法
    fun daqi()
    //非抽象方法(即提供默認(rèn)實(shí)現(xiàn)方法)
    fun defaultMethod() {
    }
}

????接口中聲明的屬性不能有幕后字段。因?yàn)榻涌谑菬o狀態(tài)的,因此接口中聲明的訪問器不能引用它們。(簡單說就是接口沒有具體的屬性,不能用幕后字段對屬性進(jìn)行賦值)

image

接口的實(shí)現(xiàn)

????Kotlin使用 : 替代Java中的extends 和 implements 關(guān)鍵字。Kotlin和Java一樣,一個類可以實(shí)現(xiàn)任意多個接口,但是只能繼承一個類。

????接口中抽象的方法和抽象屬性,實(shí)現(xiàn)接口的類必須對其提供具體的實(shí)現(xiàn)。

????對于在接口中提供默認(rèn)實(shí)現(xiàn)的接口方法和提供具體訪問器的屬性,可以對其進(jìn)行覆蓋,重新實(shí)現(xiàn)方法和提供新的訪問器實(shí)現(xiàn)。

class MyClass:MyInterface{
    //原抽象屬性,提供具體訪問器
    //不提供具體訪問器,提供初始值,使用默認(rèn)訪問器也是沒有問題的
    override var length: Int = 0
    /*override var length: Int
        get() = 0
        set(value) {}*/
    
    //覆蓋提供好訪問器的接口屬性
    override val name: String
        //super.name 其實(shí)是調(diào)用接口中定義的訪問器
        get() = super.name
    
    //原抽象方法,提供具體實(shí)現(xiàn)
    override fun daqi() {
    }

    //覆蓋默認(rèn)方法
    override fun defaultMethod() {
        super.defaultMethod()
    }
}

????無論是從接口中獲取的屬性還是方法,前面都帶有一個override關(guān)鍵字。該關(guān)鍵字與Java的@Override注解類似,重寫父類或接口的方法屬性時,都 強(qiáng)制 需要用override修飾符進(jìn)行修飾。因?yàn)檫@樣可以避免先寫出實(shí)現(xiàn)方法,再添加抽象方法造成的意外重寫。

接口的繼承

????接口也可以從其他接口中派生出來,從而既提供基類成員的實(shí)現(xiàn),也可以聲明新的方法和屬性。

interface Name {
    val name:String
}

interface Person :Name{
    fun learn()
}

class daqi:Person{
    //為父接口的屬性提供具體的訪問器
    override val name: String
        get() = "daqi"
    
    //為子接口的方法提供具體的實(shí)現(xiàn)
    override fun learn() {
    }
}

覆蓋沖突

????在C++中,存在菱形繼承的問題,即一個類同時繼承具有相同函數(shù)簽名的兩個方法,到底該選擇哪一個實(shí)現(xiàn)呢?由于Kotlin的接口支持默認(rèn)方法,當(dāng)一個類實(shí)現(xiàn)多個接口,同時擁有兩個具有相同函數(shù)簽名的默認(rèn)方法時,到底選擇哪一個實(shí)現(xiàn)呢?

主要根據(jù)以下3條規(guī)則進(jìn)行判斷:

????1、類中帶override修飾的方法優(yōu)先級最高。 類或者父類中帶override修飾的方法的優(yōu)先級高于任何聲明為默認(rèn)方法的優(yōu)先級。(Kotlin編譯器強(qiáng)制要求,當(dāng)類中存在和父類或?qū)崿F(xiàn)的接口有相同函數(shù)簽名的方法存在時,需要在前面添加override關(guān)鍵字修飾。)

????2、當(dāng)?shù)谝粭l無法判斷時,子接口的優(yōu)先級更高。優(yōu)先選擇擁有最具體實(shí)現(xiàn)的默認(rèn)方法的接口,因?yàn)閺睦^承角度理解,可以認(rèn)為子接口的默認(rèn)方法覆蓋重寫了父接口的默認(rèn)方法,子接口比父接口具體。

????3、最后還是無法判斷時,繼承多個接口的類需要顯示覆蓋重寫該方法,并選擇調(diào)用期望的默認(rèn)方法。

  • 如何理解第二條規(guī)則?先看看一下例子:

Java繼承自Language,兩者都對use方法提供了默認(rèn)實(shí)現(xiàn)。而Java比Language更具體。

interface Language{
    fun use() = println("使用語言")
}

interface Java:Language{
    override fun use() = println("使用Java語言編程")
}

而實(shí)現(xiàn)這兩個接口的類中,并無覆蓋重寫該方法,只能選擇更具體的默認(rèn)方法作為其方法實(shí)現(xiàn)。

class Person:Java,Language{
}

//執(zhí)行結(jié)果是輸出:使用Java語言編程
val daqi = Person()
daqi.use()
  • 如何理解第三條規(guī)則?繼續(xù)看例子:

接口Java和Kotlin都提供對learn方法提供了具體的默認(rèn)實(shí)現(xiàn),且兩者并無明確的繼承關(guān)系。

interface Java {
    fun learn() = println("學(xué)習(xí)Java")
}

interface Kotlin{
    fun learn() = println("學(xué)習(xí)Kotlin")
}

當(dāng)某類都實(shí)現(xiàn)Java和Kotlin接口時,此時就會產(chǎn)生覆蓋沖突的問題,這個時候編譯器會強(qiáng)制要求你提供自己的實(shí)現(xiàn):

image

唯一的解決辦法就是顯示覆蓋該方法,如果想沿用接口的默認(rèn)實(shí)現(xiàn),可以super關(guān)鍵字,并將具體的接口名放在super的尖括號中進(jìn)行調(diào)用。

class Person:Java,Kotlin{
    override fun learn() {
        super<Java>.learn()
        super<Kotlin>.learn()
    }
}

對比Java 8的接口

????Java 8中也一樣可以為接口提供默認(rèn)實(shí)現(xiàn),但需要使用default關(guān)鍵字進(jìn)行標(biāo)識。(Kotlin只需要提供具體的方法實(shí)現(xiàn),即提供函數(shù)體)

public interface Java8 {
    default void defaultMethod() {
        System.out.println("我是Java8的默認(rèn)方法"); 
    }
} 

????面對覆蓋沖突,Java8的和處理和Kotlin的基本相似,在語法上顯示調(diào)用接口的默認(rèn)方法時有些不同:

//Java8 顯示調(diào)用覆蓋沖突的方法
Java8.super.defaultMethod()
    
//Kotlin 顯示調(diào)用覆蓋沖突的方法
super<Kotlin>.learn()

Kotlin 與 Java 間接口的交互

????眾所周知,Java8之前接口沒有默認(rèn)方法,Kotlin是如何兼容的呢?定義如下兩個接口,再查看看一下反編譯的結(jié)果:

interface Language{
    //默認(rèn)方法
    fun use() = println("使用語言編程")
}


interface Java:Language{
    //抽象屬性
    var className:String

    //提供訪問器的屬性
    val field:String
        get() = ""

    //默認(rèn)方法
    override fun use() = println("使用Java語言編程")

    //抽象方法
    fun absMethod()
}

先查看父接口的源碼:

public interface Language {
   void use();

   public static final class DefaultImpls {
      public static void use(Language $this) {
         String var1 = "使用語言編程";
         System.out.println(var1);
      }
   }
}

????Language接口中的默認(rèn)方法轉(zhuǎn)換為抽象方法保留在接口中。其內(nèi)部定義了一個名為DefaultImpls的靜態(tài)內(nèi)部類,該內(nèi)部類中擁有和默認(rèn)方法相同名稱的靜態(tài)方法,而該靜態(tài)方法的實(shí)現(xiàn)就是其同名默認(rèn)函數(shù)的具體實(shí)現(xiàn)。也就是說,Kotlin的默認(rèn)方法轉(zhuǎn)換為靜態(tài)內(nèi)部類DefaultImpls的同名靜態(tài)函數(shù)。

所以,如果想在Java中調(diào)用Kotlin接口的默認(rèn)方法,需要加多一層DefaultImpls

public class daqiJava implements Language {
    @Override
    public void use() {
        Language.DefaultImpls.use(this);
    }
}

再繼續(xù)查看子接口的源碼

public interface Java extends Language {
   //抽象屬性的訪問器
   @NotNull 
   String getClassName();
   void setClassName(@NotNull String var1);

   //提供具體訪問器的屬性
   @NotNull 
   String getField();

    //默認(rèn)方法
   void use();
    
    //抽象方法
   void absMethod();
    
   public static final class DefaultImpls {
      @NotNull
      public static String getField(Java $this) {
         return "";
      }

      public static void use(Java $this) {
         String var1 = "使用Java語言編程";
         System.out.println(var1);
      }
   }
}

????通過源碼觀察到,無論是抽象屬性還是擁有具體訪問器的屬性,都沒有在接口中定義任何屬性,只是聲明了對應(yīng)的訪問器方法。(和擴(kuò)展屬性相似)

抽象屬性和提供具體訪問器的屬性區(qū)別是:

  • 抽象屬性的訪問器均為抽象方法。
  • 擁有具體訪問器的屬性,其訪問器實(shí)現(xiàn)和默認(rèn)方法一樣,外部聲明一個同名抽象方法,具體實(shí)現(xiàn)被存儲在靜態(tài)內(nèi)部類DefaultImpls的同名靜態(tài)函數(shù)中。

Java定義的接口,Kotlin繼承后能為其父接口的方法提供默認(rèn)實(shí)現(xiàn)嗎?當(dāng)然是可以啦:

//Java接口
public interface daqiInterface {
    String name = "";
    
    void absMethod();
}

//Kotlin接口
interface daqi: daqiInterface {
    override fun absMethod() {

    }
}

????Java接口中定義的屬性都是默認(rèn)public static final,對于Java的靜態(tài)屬性,在Kotlin中可以像頂層屬性一樣,直接對其進(jìn)行使用:

fun main(args: Array<String>) {
    println("Java接口中的靜態(tài)屬性name = $name")
}

????Kotlin的類可以有一個主構(gòu)造函數(shù)以及一個或多個 從構(gòu)造函數(shù)。主構(gòu)造函數(shù)是類頭的一部分,即在類體外部聲明。

主構(gòu)造方法

constructor關(guān)鍵字可以用來聲明 主構(gòu)造方法 或 從構(gòu)造方法。

class Person(val name:String)
//其等價于
class Person constructor(val name:String)

????主構(gòu)造函數(shù)不能包含任何的代碼。初始化的代碼可以放到以 init 關(guān)鍵字作為前綴的初始化塊中。

class Person constructor(val name:String){
    init {
        println("name = $name")
    }
}

????構(gòu)造方法的參數(shù)也可以設(shè)置為默認(rèn)參數(shù),當(dāng)所有構(gòu)造方法的參數(shù)都是默認(rèn)參數(shù)時,編譯器會生成一個額外的不帶參數(shù)的構(gòu)造方法來使用所有的默認(rèn)值。

class Person constructor(val name:String = "daqi"){
    init {
        println("name = $name")
    }
}

//輸出為:name = daqi
fun main(args: Array<String>) {
    Person()
}

????主構(gòu)造方法同時需要初始化父類,子類可以在其列表參數(shù)中索取父類構(gòu)造方法所需的參數(shù),以便為父類構(gòu)造方法提供參數(shù)。

open class Person constructor(name:String){
}

class daqi(name:String):Person(name){
}

????當(dāng)沒有給一個類聲明任何構(gòu)造方法,編譯器將生成一個不做任何事情的默認(rèn)構(gòu)造方法。對于只有默認(rèn)構(gòu)造方法的類,其子類必須顯式地調(diào)用父類的默認(rèn)構(gòu)造方法,即使他沒有參數(shù)。

open class View
    
class Button:View()

而接口沒有構(gòu)造方法,所以接口名后不加括號。

//實(shí)現(xiàn)接口
class Button:ClickListener

當(dāng) 主構(gòu)造方法 有注解或可見性修飾符時,constructor 關(guān)鍵字不可忽略,并且constructor 在這些修飾符和注解的后面。

class Person public @Inject constructor(val name:String)

構(gòu)造方法的可見性是 public,如果想將構(gòu)造方法設(shè)置為私有,可以使用private修飾符。

class Person private constructor()

從構(gòu)造方法

從構(gòu)造方法使用constructor關(guān)鍵字進(jìn)行聲明

open class View{
    //從構(gòu)造方法1
    constructor(context:Context){
    }
    
    //從構(gòu)造方法2
    constructor(context:Context,attr:AttributeSet){
    }
}

????使用this關(guān)鍵字,從一個構(gòu)造方法中調(diào)用該類另一個構(gòu)造方法,同時也能使用super()關(guān)鍵字調(diào)用父類構(gòu)造方法。

????如果一個類有 主構(gòu)造方法,每個 從構(gòu)造方法 都應(yīng)該顯式調(diào)用 主構(gòu)造方法,否則將其委派給會調(diào)用主構(gòu)造方法的從構(gòu)造方法。

class Person constructor(){
    //從構(gòu)造方法1,顯式調(diào)用主構(gòu)造方法
    constructor(string: String) : this() {
        println("從構(gòu)造方法1")
    }
    
    //從構(gòu)造方法2,顯式調(diào)用構(gòu)造方法1,間接調(diào)用主構(gòu)造方法。
    constructor(data: Int) : this("daqi") {
        println("從構(gòu)造方法2")
    }
}

注意

????初始化塊中的代碼實(shí)際上會成為主構(gòu)造函數(shù)的一部分。顯式調(diào)用主構(gòu)造方法會作為次構(gòu)造函數(shù)的第一條語句,因此所有初始化塊中的代碼都會在次構(gòu)造函數(shù)體之前執(zhí)行。

即使該類沒有主構(gòu)造函數(shù),這種調(diào)用仍會隱式發(fā)生,并且仍會執(zhí)行初始化塊。

//沒有主構(gòu)造方法的類
class Person{
    init {
        println("主構(gòu)造方法 init 1")
    }
    
    //從構(gòu)造方法默認(rèn)會執(zhí)行所有初始化塊
    constructor(string: String) {
        println("從構(gòu)造方法1")
    }

    init {
        println("主構(gòu)造方法 init 2")
    }
}
image

????如果一個類擁有父類,但沒有主構(gòu)造方法時,每個從構(gòu)造方法都應(yīng)該初始化父類(即調(diào)用父類的構(gòu)造方法),否則將其委托給會初始化父類的構(gòu)造方法(即使用this調(diào)用其他會初始化父類的構(gòu)造方法)。

class MyButton:View{
    //調(diào)用自身的另外一個從構(gòu)造方法,間接調(diào)用父類的構(gòu)造方法。
    constructor(context:Context):this(context,MY_STYLE){
    }
    //調(diào)用父類的構(gòu)造方法,初始化父類。
    constructor(context:Context,attr:AttributeSet):super(context,attr){
    }
}

脆弱的基類

????Java中允許創(chuàng)建任意類的子類并重寫任意方法,除非顯式地使用final關(guān)鍵字。對基類進(jìn)行修改導(dǎo)致子類不正確的行為,就是所謂的脆弱的基類。所以Kotlin中類和方法默認(rèn)是final,Java類和方法默認(rèn)是open的

????當(dāng)你允許一個類存在子類時,需要使用open修飾符修改這個類。如果想一個方法能被子類重寫,也需要使用open修飾符修飾。

open class Person{
    //該方法時final 子類不能對它進(jìn)行重寫
    fun getName(){}
    
    //子類可以對其進(jìn)行重寫
    open fun getAge(){}
}

對基類或接口的成員進(jìn)行重寫后,重寫的成員同樣默認(rèn)為open。(盡管其為override修飾)

如果想改變重寫成員默認(rèn)為open的行為,可以顯式的將重寫成員標(biāo)注為final

open class daqi:Person(){
    final override fun getAge() {
        super.getAge()
    }
}

抽象類的成員和接口的成員始終是open的,不需要顯式地使用open修飾符。

image

可見性修飾符

????Kotlin和Java的可見性修飾符相似,同樣可以使用public、protected和private修飾符。但Kotlin默認(rèn)可見性是public,而Java默認(rèn)可見性是包私有

????Kotlin中并沒有包私有這種可見性,Kotlin提供了一個新的修飾符:internal,表示“只在模塊內(nèi)部可見”。模塊是指一組一起編譯的Kotlin文件??赡苁且粋€Gradle項目,可能是一個Idea模塊。internal可見性的優(yōu)勢在于它提供了對模塊實(shí)現(xiàn)細(xì)節(jié)的封裝。

????Kotlin允許在頂層聲明中使用private修飾符,其中包括類聲明,方法聲明和屬性聲明,但這些聲明只能在聲明它們的文件中可見。

注意

  • 覆蓋一個 protected 成員并且沒有顯式指定其可見性,該成員的可見性還是 protected 。
  • 與Java不同,Kotlin的外部類(嵌套類)不能看到其內(nèi)部類中的private成員。
  • internal修飾符編譯成字節(jié)碼轉(zhuǎn)Java后,會變成public。
  • private類轉(zhuǎn)換為Java時,會變成包私有聲明,因?yàn)镴ava中類不能聲明為private。

內(nèi)部類和嵌套類

????Kotlin像Java一樣,允許在一個類中聲明另一個類。但Kotlin的嵌套類默認(rèn)不能訪問外部類的實(shí)例,和Java的靜態(tài)內(nèi)部類一樣。

????如果想讓Kotlin內(nèi)部類像Java內(nèi)部類一樣,持有一個外部類的引用的話,需要使用inner修飾符。

image

內(nèi)部類需要外部類引用時,需要使用 this@外部類名 來獲取。

class Person{
    private val name  = "daqi"
    
    inner class MyInner{
        fun getPersonInfo(){
            println("name = ${this@Person.name}")
        }
    }
}

object關(guān)鍵字

對象聲明

????在Java中創(chuàng)建單例往往需要定義一個private的構(gòu)造方法,并創(chuàng)建一個靜態(tài)屬性來持有這個類的單例。

????Kotlin通過對象聲明將類聲明和類的單一實(shí)例結(jié)合在一起。對象聲明在定義的時候就立即創(chuàng)建,而這個初始化過程是線程安全的。

????對象聲明中可以包含屬性、方法、初始化語句等,也支持繼承類和實(shí)現(xiàn)接口,唯一不允許的是不能定義構(gòu)造方法(包括主構(gòu)造方法和從構(gòu)造方法)。

????對象聲明不能定義在方法和內(nèi)部類中,但可以定義在其他的對象聲明和非內(nèi)部類(例如:嵌套類)。如果需要引用該對象,直接使用其名稱即可。

//定義對象聲明
class Book private constructor(val name:String){

    object Factory {
        val name = "印書廠"

        fun createAppleBooK():Book{
            return Book("Apple")
        }

        fun createAndroidBooK():Book{
            return Book("Android")
        }
    }
}

調(diào)用對象聲明的屬性和方法:

Book.Factory.name
Book.Factory.createAndroidBooK()

????將對象聲明反編譯成Java代碼,其內(nèi)部實(shí)現(xiàn)也是定義一個private的構(gòu)造方法,并始終創(chuàng)建一個名為INSTANCE的靜態(tài)屬性來持有這個類的單例,而該類的初始化放在靜態(tài)代碼塊中。

public final class Book {
   //....

   public Book(String name, DefaultConstructorMarker $constructor_marker) {
      this(name);
   }

   public static final class Factory {
      @NotNull
      private static final String name = "印書廠";
      public static final Book.Factory INSTANCE;

      //...

      @NotNull
      public final Book createAppleBooK() {
         return new Book("Apple", (DefaultConstructorMarker)null);
      }

      @NotNull
      public final Book createAndroidBooK() {
         return new Book("Android", (DefaultConstructorMarker)null);
      }

      private Factory() {
      }

      static {
         Book.Factory var0 = new Book.Factory();
         INSTANCE = var0;
         name = "印書廠";
      }
   }
}

用Java調(diào)用對象聲明的方法:

//Java調(diào)用對象聲明
Book.Factory.INSTANCE.createAndroidBooK();

伴生對象

????一般情況下,使用頂層函數(shù)可以很好的替代Java中的靜態(tài)函數(shù),但頂層函數(shù)無法訪問類的private成員。

????當(dāng)需要定義一個方法,該方法能在沒有類實(shí)例的情況下,調(diào)用該類的內(nèi)部方法??梢远x一個該類的對象聲明,并在該對象聲明中定義該方法。類內(nèi)部的對象聲明可以用 companion 關(guān)鍵字標(biāo)記,這種對象叫伴生對象。

????可以直接通過類名來訪問該伴生對象的方法和屬性,不用再顯式的指明對象聲明的名稱,再訪問該對象聲明對象的方法和屬性。可以像調(diào)用該類的靜態(tài)函數(shù)和屬性一樣,不需要再關(guān)心對象聲明的名稱。

//將構(gòu)造方法私有化
class Book private constructor(val name:String){
    //伴生對象的名稱可定義也可以不定義。
    companion object {
        //伴生對象調(diào)用其內(nèi)部私有構(gòu)造方法
        fun createAppleBooK():Book{
            return Book("Apple")
        }

        fun createAndroidBooK():Book{
            return Book("Android")
        }
    }
}

調(diào)用伴生對象的方法:

Book.createAndroidBooK()

????伴生對象的實(shí)現(xiàn)和對象聲明類似,定義一個private的構(gòu)造方法,并始終創(chuàng)建一個名為Companion的靜態(tài)屬性來持有這個類的單例,并直接對Companion靜態(tài)屬性進(jìn)行初始化。

public final class Book {
   //..
   public static final Book.Companion Companion = new Book.Companion((DefaultConstructorMarker)null);

    //...

   public static final class Companion {
     //...
      @NotNull
      public final Book createAppleBooK() {
         return new Book("Apple", (DefaultConstructorMarker)null);
      }

      @NotNull
      public final Book createAndroidBooK() {
         return new Book("Android", (DefaultConstructorMarker)null);
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

伴生對象的擴(kuò)展

????擴(kuò)展方法機(jī)制允許在任何地方定義某類的擴(kuò)展方法,但需要該類的實(shí)例進(jìn)行調(diào)用。當(dāng)需要擴(kuò)展一個通過類自身調(diào)用的方法時,如果該類擁有伴生對象,可以通過對伴生對象定義擴(kuò)展方法。

//對伴生對象定義擴(kuò)展方法
fun Book.Companion.sellBooks(){
}

當(dāng)對該擴(kuò)展方法進(jìn)行調(diào)用時,可以直接通過類自身進(jìn)行調(diào)用:

Book.sellBooks()

匿名內(nèi)部類

作為android開發(fā)者,在設(shè)置監(jiān)聽時,創(chuàng)建匿名對象的情況再常見不過了。

mButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        
    }
});

????object關(guān)鍵字除了能用來聲明單例式對象外,還可以聲明匿名對象。和對象聲明不同,匿名對象不是單例,每次都會創(chuàng)建一個新的對象實(shí)例。

mRecyclerView.setOnClickListener(object :View.OnClickListener{
    override fun onClick(v: View?) {
        
    }
});

????當(dāng)該匿名類擁有兩個以上抽象方法時,才需要使用object創(chuàng)建匿名類。否則盡量使用lambda表達(dá)式。

mButton.setOnClickListener {
}

參考文獻(xiàn):

android Kotlin系列:

Kotlin知識歸納(一) —— 基礎(chǔ)語法

Kotlin知識歸納(二) —— 讓函數(shù)更好調(diào)用

Kotlin知識歸納(三) —— 頂層成員與擴(kuò)展

Kotlin知識歸納(四) —— 接口和類

Kotlin知識歸納(五) —— Lambda

Kotlin知識歸納(六) —— 類型系統(tǒng)

Kotlin知識歸納(七) —— 集合

Kotlin知識歸納(八) —— 序列

Kotlin知識歸納(九) —— 約定

Kotlin知識歸納(十) —— 委托

Kotlin知識歸納(十一) —— 高階函數(shù)

Kotlin知識歸納(十二) —— 泛型

Kotlin知識歸納(十三) —— 注解

Kotlin知識歸納(十四) —— 反射

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。