前言
在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()
複製程式碼