Kotlin —— 扩展函数

一、前言

Kotlin中的扩展函数特性让我们的代码变得更加简单和整洁。扩展函数是Kotlin语言中独有的新特性,利用它可以减少很多的样板代码,大大提高开发的效率;此外扩展函数的使用也是非常简单的。我会从以下几个方面阐述Kotlin中的扩展函数:

  • 为什么要使用Kotlin中的扩展函数?
  • 怎么去使用扩展函数和扩展属性?
  • 什么是扩展函数和属性?
  • 扩展函数和成员函数区别
  • 扩展函数不可以被重写

二、为什么要使用Kotlin中的扩展函数?

Koltin可以与Java有非常好的互操作性,所以扩展函数这个新特性可以很平滑与现有Java代码集成。甚至纯Kotlin的项目都可以基于Java库,甚至Android中的一些框架库,第三方库来构建。扩展函数非常适合Kotlin和Java语言混合开发模式。在很多公司一些比较稳定良好的库都是Java写,也完全没必要去用Kotlin语言重写。但是想要扩展库的接口和功能,这时候扩展函数可能就会派上用场。使用Kotlin的扩展函数还有一个好处就是没有副作用,不会对原有库代码或功能产生影响。先来看下扩展函数长啥样:

// GlobalExtendFunc.kt

fun String.wrap(): String {
    return "前缀-${this}-后缀"
}
val test = "Test".wrap()

Tools -> Kotlin -> Show Kotlin Bytecode,查看转成 java 的代码:

public final class GlobalExtendFuncKt {
   @NotNull
   private static final String test = wrap("Test");

   @NotNull
   public static final String wrap(@NotNull String $this$wrap) {
      Intrinsics.checkParameterIsNotNull($this$wrap, "$this$wrap");
      return "前缀-" + $this$wrap + "-后缀";
   }

   @NotNull
   public static final String getTest() {
      return test;
   }
}

我们看到:

  • Kotlin会生成一个类,类名:文件名 + Kt;且是 final 修饰;
  • 会生成两个方法,实际就是 set / get 方法,并且是静态常量方法;

如果上面的扩展函数例子没有情景,不访我们换个和 Android 相关的例子:

// GlobalExtendFunc.kt
fun TextView.isBold() = this.apply {
    paint.isFakeBoldText = true
}
val textView = TextView(null).isBold()

转成 java 后如下:

public final class GlobalExtendFuncKt {
   @NotNull
   private static final TextView textView = isBold(new TextView((Context)null));

   @NotNull
   public static final TextView isBold(@NotNull TextView $this$isBold) {
      Intrinsics.checkParameterIsNotNull($this$isBold, "$this$isBold");
      boolean var2 = false;
      boolean var3 = false;
      int var5 = false;
      TextPaint var10000 = $this$isBold.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "paint");
      var10000.setFakeBoldText(true);
      return $this$isBold;
   }

   @NotNull
   public static final TextView getTextView() {
      return textView;
   }
}

三、怎么去使用扩展函数和扩展属性

3.1、扩展函数的基本使用

只需要把扩展的类或者接口名称,放到即将要添加的函数名前面。这个类或者名称就叫做接收者类型,类的名称与函数之间用"."调用连接。this指代的就是接收者对象,它可以访问扩展的这个类可访问的方法和属性。

注意: 接收者类型是由扩展函数定义的,而接收者对象正是这个接收者类型的对象实例,那么这个对象实例就可以访问这个类中成员方法和属性,所以一般会把扩展函数当做成员函数来用。

3.2、扩展属性的基本使用

扩展属性实际上是提供一种方法来访问属性而已,并且这些扩展属性是没有任何的状态的,因为不可能给现有Java库中的对象额外添加属性字段,只是使用简洁语法类似直接操作属性,实际上还是方法的访问。
扩展属性的定义(必需定义 get / set):

var TextView.isBold: Boolean
    get() {
        return this.paint.isFakeBoldText
    }
    set(value) {
        this.paint.isFakeBoldText = true
    }

转成 java 后如下:

public final class GlobalExtendFuncKt {
   public static final boolean isBold(@NotNull TextView $this$isBold) {
      Intrinsics.checkParameterIsNotNull($this$isBold, "$this$isBold");
      TextPaint var10000 = $this$isBold.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "this.paint");
      return var10000.isFakeBoldText();
   }

   public static final void setBold(@NotNull TextView $this$isBold, boolean value) {
      Intrinsics.checkParameterIsNotNull($this$isBold, "$this$isBold");
      TextPaint var10000 = $this$isBold.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "this.paint");
      var10000.setFakeBoldText(true);
   }
}

注意:

  • 扩展属性和扩展函数定义类似,也有接收者类型和接收者对象,接收者对象也是接收者类型的一个实例,一般可以把它当做类中成员属性来使用。
  • 必须定义get()方法,在Kotlin中类中的属性都是默认添加get()方法的,但是由于扩展属性并不是给现有库中的类添加额外的属性,自然就没有默认get()方法实现之说。所以必须手动添加get()方法。
  • 由于重写了set()方法,说明这个属性访问权限是可读和可写,需要使用var

四、什么是扩展函数和属性

我们从上面例子可以看出,kotlin的扩展函数真是强大,可以毫无副作用给原有库的类增加属性和方法,比如例子中TextView,我们根本没有去动TextView源码,但是却给它增加一个扩展属性和函数。具有那么强大功能,到底它背后原理是什么?
上面每个例子,我们都给出了转成 java 后的代码,一目了然!

4.1、扩展函数实质原理

我们再贴个例子来回顾下:

public final class GlobalExtendFuncKt { // Kotlin文件名 + Kt 组成的类名
   @NotNull
   public static final TextView isBold(@NotNull TextView $this$isBold) {
      // 扩展函数isBold对应实际上是Java中的静态函数,并且传入一个接收者类型对象作为参数
      // 接收参数是:$this$函数名
      Intrinsics.checkParameterIsNotNull($this$isBold, "$this$isBold");
      boolean var2 = false;
      boolean var3 = false;
      int var5 = false;
      TextPaint var10000 = $this$isBold.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "paint");
      var10000.setFakeBoldText(true);
      
      // 最后返回这个接收者对象自身,以致于我们在Kotlin中完全可以使用this替代接收者对象或者直接不写
      return $this$isBold;
   }
}

4.2、Java中调用Kotlin中定义的扩展函数

分析完Kotlin中扩展函数的原理,我们也就很清楚,如何在Java中去调用Kotlin中定义好的扩展函数了,实际上使用方法就是静态函数调用,和我们之前讲的顶层函数在Java中调用类似,不过唯一不同是需要传入一个接收者对象参数。

GlobalExtendFuncKt.isBold(textView)

4.3、扩展属性的原理

我们在 3.2 中看到,实际就是生成了一个 set / get 方法,这两方法是静态函数。

4.4、Java中调用Kotlin中定义的扩展属性

类似 4.2。

五、扩展函数和成员函数区别

说到扩展函数和成员函数的区别,通过上面例子我们已经很清楚了,这里做个归纳总结:

  • 扩展函数和成员函数使用方式类似,可以直接访问被扩展类的方法和属性;
    (传入了一个扩展类的对象,内部实际上是用实例对象去访问扩展类的方法和属性)
  • 扩展函数不能打破扩展类的封装性,不能像成员函数一样直接访问内部私有函数和属性;
    (扩展函数访问实际是类的对象访问,由于类的对象实例不能访问内部私有函数和属性,自然扩展函数也就不能访问内部私有函数和属性了)
  • 扩展函数实际上是一个静态函数是处于类的外部,而成员函数则是类的内部函数;
  • 父类成员函数可以被子类重写,而扩展函数则不行;

六、扩展函数不可以被重写

在Kotlin和Java中我们都知道类的成员函数是可以被重写的,子类是可以重写父类的成员函数,但是子类是不可以重写父类的扩展函数。

open class Fruit {
    open fun name() = "This is Fruit class"
}
class Apple : Fruit() {
    // 重写父类方法
    override fun name() = "This is Apple class"
}

// 父类和子类的扩展函数
fun Fruit.type() = "typeof Fruit"
fun Apple.type() = "typeof Apple"

fun main(args: Array) {
    val fruit: Fruit = Apple()
    println("成员函数测试: ${fruit.name()}")
    println("扩展函数测试: ${fruit.type()}")
}

// 打印结果:
// 成员函数测试: This is Apple class
// 扩展函数测试: typeof Fruit

以上运行结果再次说明了扩展函数并不是类的一部分,它是声明与类外部的,尽管子类和父类拥有了相同的扩展函数,但是实际上扩展函数是静态函数。从编译内部来看,子类和父类拥有了相同的扩展函数,实际上就是定义两个同名的静态扩展函数分别传入父类对象和子类对象,那么调用的方法肯定也是父类中的方法和子类中的方法,所以输出肯定是父类的。

你可能感兴趣的:(Kotlin —— 扩展函数)