欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Kotlin实战--给别人的类添加方法:扩展函数和属性

程序员文章站 2022-03-09 08:13:42
...

1.概述

扩展函数能够让我们与项目无缝接入,我们能够在无侵入的情况下为类添加更多的行为和属性,Kotlin库中很多地方就用到了扩展函数,如Anko库里面的布局系统,集合的使用等。正是由于扩展函数的使用,才可以让我们与JAVA无缝衔接。并且扩展函数的使用非常的简单,下面我将从下面几个方面介绍扩展函数。

  1. 为什么要使用Kotlin中的扩展函数?
  2. 如何使用扩展函数和扩展属性?
  3. 扩展函数和属性原理
  4. 扩展函数和成员函数区别
  5. 扩展函数不可以被重写

2.为什么要使用Kotlin中的扩展函数

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

3.如何使用扩展函数和扩展属性

下面我将具体介绍扩展函数扩展属性

3.1扩展函数的基本使用

下面我们以一个例子来引入

给TextView加粗

//添加扩展函数
fun TextView.setBold() = this.apply {
    this.paint.isFakeBoldText = true
}

//继承TextView
open class TextViewExtension(context: Context?, attrs: AttributeSet?) : TextView(context, attrs) {

}

调用

textView.setBold()  //textView为TextView的实例,不一定为TextViewExtension的实例。

大家是不是觉得奇怪,在类外面定义行为(Java中属性和行为都属于类,定义在类里面),这里大家可以看下Kotlin语法糖–顶层函数

你所有要做的,就是把你要扩展的类或者接口的名称,放到即将添加的函数前面。这个类的名称被称为接收者类型;用来调用这个扩展函数的那个对象,叫作接收者对象,如下图

Kotlin实战--给别人的类添加方法:扩展函数和属性

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

3.2扩展属性的基本使用

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

//添加扩展属性
var TextView.textSizeS: Float
    get() {//必须定义get()方法,因为不是类的成员变量,也自然就没有了默认的get()实现
        return paint.textSize
    }
    set(value) {
        paint.textSize = value
    }
    
//继承TextView
open class TextViewExtension(context: Context?, attrs: AttributeSet?) : TextView(context, attrs) {

}

调用

textView.textSizeS=60F // 这里textSize是TextView默认方法,不能一样,否则以成员函数为主。并且这里单位是px,成员函数单位是sp。

总结:

  1. 扩展属性和扩展函数定义类似,也有接收者类型和接收者对象,接收者对象也是接收者类型的一个实例,一般可以把它当做类中成员属性来使用。

  2. 必须定义get()方法,在Kotlin中类中的属性都是默认添加get()方法的,但是由于扩展属性并不是给现有库中的类添加额外的属性,自然就没有默认get()方法实现之说。所以必须手动添加get()方法。

  3. 由于重写了set()方法,说明这个属性访问权限是可读和可写,需要使用var

4.扩展函数和属性原理

我们从上面例子可以看出,kotlin的扩展函数真是强大,可以毫无副作用给原有库的类增加属性和方法,比如例子中TextView,我们根本没有去动TextView源码,但是却给它增加一个扩展属性和函数。具有那么强大功能,到底它背后原理是什么?其实很简单,通过decompile看下反编译后对应的Java代码就一目了然了。

4.1扩展函数原理

扩展函数实际上就是一个对应Java中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的。

public final class TextViewExtensionKt {
   @NotNull
   public static final TextView setBold(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      int var3 = false;
      TextPaint var10000 = $receiver.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "this.paint");
      var10000.setFakeBoldText(true);
      return $receiver;
   }
}

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

TextViewExtensionKt.setBold(findViewById(R.id.tv_text))

4.2扩展属性原理

扩展属性实际上就是提供某个属性访问的set,get方法,这两个set,get方法是静态函数,同时都会传入一个接收者类型的对象,然后在其内部用这个对象实例去访问和修改对象所对应的类的属性。

public final class TextViewExtensionKt {
   public static final float getTextSizeS(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      TextPaint var10000 = $receiver.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "paint");
      return var10000.getTextSize();
   }

   public static final void setTextSizeS(@NotNull TextView $receiver, float value) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      TextPaint var10000 = $receiver.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "paint");
      var10000.setTextSize(value);
   }
}

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

Java调用Kotlin中定义的扩展属性也很简单,就相当于直接调用生成的set(),get()方法一样。

 TextViewExtensionKt.setTextSizeS(findViewById(R.id.tv_text),40);

5.扩展函数和成员函数区别

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

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

6.扩展函数不可以被重写

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

open class Animal {
    open fun shout() = println("animal is shout")//定义成员函数
}

class Cat: Animal() {
    override fun shout() {
        println("Cat is shout")//子类重写父类成员函数
    }
}

//定义子类和父类扩展函数
fun Animal.eat() = println("Animal eat something")

fun Cat.eat()= println("Cat eat fish")

//测试
fun main(args: Array<String>) {
    val animal: Animal = Cat()
    println("成员函数测试: ${animal.shout()}")
    println("扩展函数测试: ${animal.eat()}")
}

运行结果:

Kotlin实战--给别人的类添加方法:扩展函数和属性

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