kotlin 扩展(扩展函数和扩展属性)

5,921 阅读4分钟

前言

在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…