扩展函数--给别人的类添加方法

234 阅读5分钟

前言

Kotlin 的特色之一,就是可以平滑的和现有代码进行集成。Kotlin 项目可以基于 java 库构建,当我们在一个现有的 java 项目中集成 kotlin 的时候,需要面临现有代码不能转为 Kotlin 的局面,当使用某些 API 的时候,如果不用重写,就能使用 Kotlin 带来的便利,岂不是一个非常好的渠道。下面,就来讲一讲扩展函数是如何实现的。

定义

理论上说,扩展函数其实非常简单,就是一个类的成员函数,不过是定义在类的外面。我们用一个例子来展示一下。我们添加一个方法计算一个字符串的最后一个字符。

fun String.lastChar():Char = this[this.length - 1]

如上所示,我们所要做的,就是把要扩展的类或者接口的名称,放到即将添加的函数前面,这个类的名称被称为 接收者类型,用来调用这个扩展函数的那个对象,叫做 接收者对象

image.png

当我们调用的时候,就可以像调用类的普通成员函数一样去调用这个函数。

println("test".lastChar())

这个例子中, String 就是接收者类型, “test” 就是接收者对象。在这个扩展函数中,可以像其他成员函数一样使用 this,也可以像普通成员函数一样,省略使用 this。

fun String.lastChar():Char = get(length - 1)

在扩展函数中,可以直接访问被扩展的类的其他方法和属性,就像在类自己的方法中访问一样。但是扩展函数不允许打破它的封装性,扩展函数不能访问私有的或者受保护的成员。

正确的使用

命名冲突问题

对于我们定义的扩展函数,它不会自动在整个项目中生效,如果需要使用这个扩展函数,需要对其进行导入。为了偶然性的命名冲突,Kotlin 允许用和导入类一样的语法导入单个的函数。

image.png 在不同的包中,有一些重名的函数时,在导入时为其重新命名就显得很有必要,这样就可以在同一个文件中去使用它们。所以在导入时使用关键字 as,就可以解决命名冲突的问题,如下图:

image.png

java 中调用扩展函数

再重申一遍,实质上,扩展函数是静态函数,它把调用对象作为它的第一个参数。看一下上面示例所对应的 java 代码。

image.png

调用扩展函数,不会创建适配的对象或者运行时的额外消耗。这就是从 java 中调用 kotlin 的扩展函数变得非常简单,就相当于调用一个类的静态函数,然后传入接收者对象就可以了。当然静态类的名称由我们定义扩展函数的文件名决定:xxxKt

char test = StringUtilKt.lastChar("test");

不能重写的扩展函数

在项目开发中,我们经常会重写成员函数。比如 MyView 和 MyButton,MyButton 是 MyView 的子类,假如我们现在在 MyButton 中重写了父类的 click 函数。

image.png

image.png

当我们声明了类型为 MyView 的变量,其可以被赋值为 MyButton 类型的对象,当我们调用 click 函数的时候,如果这个函数被重写了,那么则会调用到 MyButton 中重写的函数。

fun test(){
    var view:MyView = MyButton()
    view.click()
}

输出为 “myButton click”

但是对于扩展函数,则不是这样的,扩展函数并不是类的一部分,它声明再类的外面。尽管可以给基类和子类都分别定义一个同名的扩展函数,当这个函数被调用时,它是由该变量的静态类型所决定的,而不是这个变量的运行时类型。


fun MyView.showOff() = println("myView showOff")

fun MyButton.showOff() = println("MyButton showOff")

以上,我们定义了同名的两个扩展函数,

fun test(){
    var view:MyView = MyButton()
    view.showOff()
}

然后进行调用,输出为 “myView showOff”

这也就说明,扩展函数并不存在重写,因为 kotlin 会把它们当做静态函数对待。

扩展属性

扩展属性提供了一种方法,用来扩展类的 API,可以用来访问属性,但它们没有任何状态,因为没有合适的地方对其进行存储,不可能给现有的 Java 对象的实例添加额外的字段。下面我们来定义一个扩展属性。仿照扩展函数。

val String.firstChar:Char
    get() = get(0)

可以看到,和扩展函数一样,扩展属性也像接收者的一个普通的成员属性一样。 这里,必须定义 getter 函数,因为没有支持字段,因此没有默认 getter 的实现。同理,初始化也不可以,因为没有地方存储初始值。

如果在 StringBuilder 上定义一个相同的属性,可以设置为 var,因为 StringBuilder 的内容是可变的。

var StringBuilder.lastChar: Char
    get() = get(length-1)
    set(value) {
        this.setCharAt(length-1,value)
    }

然后我们就可以像访问成员函数一样去访问它。

val sb = StringBuilder("test?")
val lastChar = sb.lastChar
sb.lastChar = '!'

如果从 Java 中访问扩展属性的时候,应该显示的调用它的 getter 函数

char c = StringUtilKt.getFirstChar("test");

结语

熟练运用扩展函数,可以帮助我们构建更灵活的应用,也可以对现有项目进行更好的扩展,这也是 kotlin 一个很重要的属性,当然这也是 kotlin 学习过程中非常基础的一个部分,希望这篇文章对你有用。