Kotlin深入浅出系列------扩展

369 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第二天,点击查看活动详情 

Kotlin高阶语法糖之扩展 | 8月更文挑战

Kotlin 可以对一个类的属性和方法进行扩展,且不用继承或Decorator模式
注意:扩展是一种静态行为,对被扩展的类代码本身不会造成任何影

扩展方法

扩展函数可以在已有的类中添加新的方法,不会对原有的类修改
格式:

fun 类名.方法名(参数):返回值{
    方法体
}

以String为例扩展addStr方法为例:

fun String.addStr(str:String):String{
    return this + str
}

fun main(args: Array<String>) {
    val str1 = "Hello "
    val result = str1.addStr("world")
    println(result)
}

输出:

    hello world

扩展函数静态解析

扩展函数是静态解析的,并不是被扩展类的虚拟成员,在调用扩展函数时,具体调用的是哪一个函数,是由函数的对象表达式来决定的,而不是动态的类型来决定的

//父类
open class Animal{
}

//继承
class  Dog:Animal(){
}

fun Dog.eat(){
    println("dog  eat")
}

fun Animal.eat(){
    println("animal  eat")
}

fun printAnimal(animal: Animal) {
    println(animal.eat())  // 类型是 Animal 类
}

fun main(args: Array<String>) {
    val dog = Dog()
    printAnimal(dog)
}

输出:

    animal  eat

解析:
输出的结果是animal的扩展方法eat,没有调用dog的扩展方法eat,是因为扩展方法最后转为字节码的时候是 static final的方法,并不是给类添加方法,所以不会有多态的现象,具体请看下面

扩展方法转换成字节码后代码

public static final void eat(@NotNull Dog $this$eat) {
    Intrinsics.checkNotNullParameter($this$eat, "$this$eat");
    String var1 = "dog  eat";
    boolean var2 = false;
    System.out.println(var1);
}


public static final void eat(@NotNull Animal $this$eat) {
    Intrinsics.checkNotNullParameter($this$eat, "$this$eat");
    String var1 = "animal  eat";
    boolean var2 = false;
    System.out.println(var1);
}

解析:
如上面代码所示,扩展方法会变成以扩展对象的实例为参数的static final的方法,所以在调用方法的时候只会找到对应参数的方法,并不会有多态的效果

扩展空对象

在扩展方法中,也是可以通过this开判断接受是否威NUll这样,即使接受者为NULl,也是可以调用扩展方法。
例子:

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}
fun main(arg:Array<String>){
    var t = null
    println(t.toString())
}

输出:

    null

扩展属性

在使用扩展属性的中,扩展属性没有实际将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么块或者拿属性不能有初始化器。他们的行为只能有显示提供的getters、setters的定义

扩展属性不能有初始化器的意思就是不能有初始化
例如:

val User.age = 18   //错误: 扩展属性不能有初始化器

格式:

val 被扩展类.扩展属性名: 返回值
    get = 方法体

//实际例子
val String.getLastLenght : Int
    get = lenght -1  //lenght string自带的属性

伴生对象的扩展

如果一个类定义的有伴生对象,你也可以为伴生对象定义扩展函数和属性
格式:

fun 类名.Companion.扩展方法名(参数):返回值{
}

例子:

class MyClass{
    companion object{
    }
}

//扩展伴生对象方法
fun MyClass.Companion.foo{
    println("伴随对象的扩展函数")
}

//扩展伴生对象属性
val MyClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    MyClass.foo()
}

输出:

no:10
伴随对象的扩展函数

扩展的作用域

大多数时候我们在顶层定义扩展---直接在包里

package org.example.declarations
 
fun List<String>.getLongestString() { /*……*/}

要使用所定义包之外的一个扩展,我们需要在调用方导入它:

package org.example.usage
//导入扩展方法的包+方法方法名
import org.example.declarations.getLongestString

fun main() {
    val list = listOf("red", "green", "blue")
    list.getLongestString()
}

扩展声明为成员

在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个隐式接受者---其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为分发接受者,扩展方法调用所在的接受者类型的实例称为扩展接受者

例子:

class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}

class Connection(val host: Host, val port: Int) {
    fun printPort() { print(port) }

    fun Host.printConnectionString() {
        printHostname()   // 调用 Host.printHostname()
        print(":")
        printPort()   // 调用 Connection.printPort()
    }

    fun connect() {
        /*……*/
        host.printConnectionString()   // 调用扩展函数
    }
}

fun main() {
    Connection(Host("kotl.in"), 443).connect()
    //Host("kotl.in").printConnectionString(443)  // 错误,该扩展函数在 Connection 外不可用
}

对于分发接受者与扩展接受者的成员名称冲突情况,扩展接受者优先(被扩展的类,例如上面Host类)。要引用分发接受者的成员你可以使用this里指定

class Connection {
    fun Host.getConnectionString() {
        toString()                  // 调用 Host.toString()
        this@Connection.toString()  // 调用 Connection.toString()
    }
}

声明为成员的扩展可以声明为open并在子类覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于魁战接受者类型是静态的
例如:

open class Base { }

class Derived : Base() { }

open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("Base extension function in BaseCaller")
    }

    open fun Derived.printFunctionInfo() {
        println("Derived extension function in BaseCaller")
    }

    fun call(b: Base) {
        b.printFunctionInfo()   // 调用扩展函数
    }
}

class DerivedCaller: BaseCaller() {
    override fun Base.printFunctionInfo() {
        println("Base extension function in DerivedCaller")
    }

    override fun Derived.printFunctionInfo() {
        println("Derived extension function in DerivedCaller")
    }
}

fun main() {
    BaseCaller().call(Base())   // “Base extension function in BaseCaller”
    DerivedCaller().call(Base())  // “Base extension function in DerivedCaller”——分发接收者虚拟解析
    DerivedCaller().call(Derived())  // “Base extension function in DerivedCaller”——扩展接收者静态解析
}

解析

在我们转成java字节码后,这里扩展是非静态代码,所以和多态是一样的。

这里是转换字节码后代码:

public class BaseCaller {
   public void printFunctionInfo(@NotNull Base $this$printFunctionInfo) {
      Intrinsics.checkNotNullParameter($this$printFunctionInfo, "$this$printFunctionInfo");
      String var2 = "Base extension function in BaseCaller";
      boolean var3 = false;
      System.out.println(var2);
   }

   public void printFunctionInfo(@NotNull Derived $this$printFunctionInfo) {
      Intrinsics.checkNotNullParameter($this$printFunctionInfo, "$this$printFunctionInfo");
      String var2 = "Derived extension function in BaseCaller";
      boolean var3 = false;
      System.out.println(var2);
   }

   public final void call(@NotNull Base b) {
      Intrinsics.checkNotNullParameter(b, "b");
      this.printFunctionInfo(b);
   }
}
public final class DerivedCaller extends BaseCaller {
   public void printFunctionInfo(@NotNull Base $this$printFunctionInfo) {
      Intrinsics.checkNotNullParameter($this$printFunctionInfo, "$this$printFunctionInfo");
      String var2 = "Base extension function in DerivedCaller";
      boolean var3 = false;
      System.out.println(var2);
   }

   public void printFunctionInfo(@NotNull Derived $this$printFunctionInfo) {
      Intrinsics.checkNotNullParameter($this$printFunctionInfo, "$this$printFunctionInfo");
      String var2 = "Derived extension function in DerivedCaller";
      boolean var3 = false;
      System.out.println(var2);
   }
}