kotlin 擴充套件(擴充套件函式和擴充套件屬性)

醉過才知酒濃發表於2019-02-26

前言

在java中我們需要擴充套件一個類的新功能時,一般是繼承該類或者使用像裝飾者這樣的設計模式來實現的。
如下:

public class Animal {
    protected String name;
    Animal(String name){
        this.name = name;
    }

    public void showName(){
        System.out.print(name);
    }
}
複製程式碼

我們想要給這個 類加一個吃東西的功能,這時使用Java繼承來實現
實現程式碼如下:

    public class Cat extends Animal {
    Cat(String name) {
        super(name);
    }
    //新增加吃東西的功能
    public void eat(String food){
        System.out.print(name+":吃了 "+food);
    }

}
複製程式碼

這樣我們就實現了 吃東西的功能。
而在kotlin我們不用這樣實現了,可以通過叫做 ++擴充套件++ 的特殊宣告來完成。Kotlin 支援 ++擴充套件函式++ 和 ++擴充套件屬性++。

1.擴充套件函式

宣告一個擴充套件函式我們需要用一個 被擴充套件的類來作為它的字首。
公式如下:

fun 被擴充套件類名.擴充套件函式名( 引數 ){

//實現程式碼

}

我們來實現上面java實現的功能,程式碼如下:

fun Animal.eat(food:String){
    print("$name  吃了  $food")
}

複製程式碼

上面kotlin程式碼就實現了我們用java繼承實現的新功能,那要怎麼呼叫呢 我們可以在kotlin中像呼叫普通的函式來呼叫這個擴充套件函式,程式碼如下:

 val animal = Animal("cat")
 animal.eat("apple")
複製程式碼

上面介紹了kotlin中怎麼呼叫擴充套件函式,那在Java中怎麼呼叫了,程式碼如下:

        Animal animal = new Animal("cat");
        //java 中呼叫kotlin 擴充套件函式  Aaa 為擴充套件函式檔名
        AaaKt.eat(animal,"apple");
複製程式碼

這樣我們就在不用繼承該類的情況下增加了 吃東西的新功能。

注意: 我們可以從上面的java呼叫中可以看出,擴充套件並不是真正的修改了被擴充套件類。而只是在kotlin中的呼叫像是修改了被擴充套件類。

1.2.擴充套件是靜態解析的

在kotlin中擴充套件是靜態分發的,不是根據接收者型別的虛方法。也就是說呼叫擴充套件函式是由呼叫所在的表示式型別來決定的,而不是由表示式執行時求值結果決定的。例如:

    //擴充套件了 Animal  和Cat   Cat 繼承 Animal 
    fun Animal.getFood() = "蘋果"
    fun Cat.getFood() = "貓糧"
    //在這個方法中  傳入型別是 Animal  所以獲取到的是"蘋果",不管是傳入的是Animal 還是 Animal 的子類 如Cat
    fun printFood(food:Animal){
        print(food.getFood())
    }
    
    fun main(args: Array<String>) {
      val cat = Cat("cat")
      //如我在這傳入的是 Cat  但是 輸出出來的還是 蘋果
        printFood(cat)
    }
    
複製程式碼

在這個例子會輸出“蘋果”,因為我們在呼叫擴充套件函式時只取決於引數 food 的宣告型別,該型別是 Animal 類。

如果擴充套件函式和被擴充套件類中的成員函式有相同的接收型別、名字和引數,那麼這種情況下 ** 總是取成員**。例如:

class Dog {

    fun showName(){
        print("Dog")
    }
}

fun Dog.showName(){
    print("Cat")
}

fun main(args: Array<String>) {
    Dog().showName()
}
    
複製程式碼

如果我們呼叫 Dog 類的 showName(),它將輸出“Dog”,而不是“Cat”.

這樣是不是很不方便,所以擴充套件函式可以過載相同名字但是不同簽名成員函式。例如:

 class Dog {

    fun showName(){
        print("Dog")
    }
}


fun Dog.showName(name:String){
    print("Cat")
}
fun main(args: Array<String>) {
    Dog().showName("")
}
複製程式碼

這樣我們呼叫 showName("")函式,就會輸出 “Cat”了。

1.3 可空接受者

擴充套件函式可以為可空的接收者型別定義擴充套件。這樣的擴充套件可以在物件變數上呼叫,即使其值為 null,並且可以在函式體內檢測 this == null ,這樣就可以在沒有檢測 null 的時候呼叫成員函式了,列如:


fun Dog?.toString():String{
    if (this == null) return "null \n"
    return toString()
}

fun main(args: Array<String>) {
    var dog:Dog? = null
    print(dog.toString())
    dog  = Dog()
    print(dog.toString())
}

複製程式碼

這段程式碼 第一次列印輸出的是 "null \n" 第二次輸入的是改物件的地址。

2.擴充套件屬性

與函式類似,Kotlin也支援擴充套件屬性。 例如:

class Snake{
    var aaa = 1
}

var Snake.size:Int
    set(value) {aaa = value}
    get() = aaa +1

fun main(args: Array<String>) {
    val snake = Snake()
    print(snake.size)
    snake.size = 3
    print(snake.size)

}
複製程式碼

如上例子所示。
**注意:**由於擴充套件是沒有實際將變數成員插入類中,因此對擴充套件屬性來說幕後欄位是無效的。所以擴充套件屬性不能有初始化器,只能顯示的提供 getters/setters 定義。

例如:

//錯誤:擴充套件屬性不能有初始化
val Snake.bbb = 1
複製程式碼

如上例子是錯誤的。

3.伴生物件的擴充套件

我們知道在kotlin中是沒有 static 這個關鍵字的,那我們要怎麼實現靜態變數呢,那就用到了伴生物件,例如:

class Snake{
    var aaa = 1
    companion object {
        var Bbb = 1
    }
}
fun main(args: Array<String>) {
    //這樣就達到了Java中靜態變數一樣的效果了
    Snake.Bbb
}
複製程式碼

那我們怎麼對伴生物件進行擴充套件呢,其實也很簡單只是比其他擴充套件中間加了一個 Companion 。如例:

    fun Snake.Companion.foo(){...}
複製程式碼

如上例,我就就定義了一個名為 foo() 的擴充套件函式,那我們這呼叫這個擴充套件函式了,其實和普通的擴充套件函式呼叫方法是一樣的,如下例:

    Snake.foo()
複製程式碼

示例地址:github.com/tao11122233…

相關文章