为什么要使用扩展?
在 Java 中,我们习惯于把通用代码封装到工具类中,诸如 StringUtils、ViewUtils 等,例如:
StringUtils.java
public static void firstChar(String str) {
...
}
在使用时,我们就需要调用StringUtils.firstChar(str)。然而,这种传统的调用方式不够简单直接,表意性也不够强,会让调用方忽略 String 和 firstChar() 间的强联系。另外,调用方也希望省略 StringUtils 类名,让 firstChar() 看起来更像是 String 内部的一个属性和方法,像这样:"str".firstChar()。
要实现这种方式,在 Java 中就需要修改或继承 String 类,然而 String 是 JDK 中的 final 类,不能修改或继承。
这个时候可以使用 Kotlin 扩展来解决这个问题,我们可以把 firstChar 定义为 String 的扩展函数:
StringUtils.kt
定义 String 的扩展函数
fun String.firstChar() {
...
}
此时,在使用时可以采用"str".firstChar()的方式。在这里我们扩展了 String 类,却没有修改或继承 String。
总结:扩展是 Kotlin 中的一种特性,可以 在不修改类 / 不继承类的情况下,向一个类添加新函数或者新属性,更符合开闭原则。
开闭原则(OCP,Open Closed Principle)
开闭原则是面向对象软件设计的原则之一,即:对扩展开放,而对修改是封闭的。
扩展函数 & 扩展属性
声明扩展
声明扩展非常简单,只需要在声明时增加「类或者接口名」。这个类的名称称为 接收者类型(receiver type),调用这个扩展的对象称为 接收者对象。大多数情况下,扩展会声明为「顶级成员」,例如:
Utils.kt
声明扩展函数:
fun <T : Any?> MutableList<T>.exchange(fromIndex: Int, toIndex: Int) {
val temp = this[fromIndex]
this[fromIndex] = this[toIndex]
this[toIndex] = temp
}
声明扩展属性:
val MutableList<Int>.sumIsEven
get() = this.sum() % 2 == 0
在使用时,就可以直接像使用普通成员函数 / 属性一样:
xxx.kt
val list = mutableListOf(1,2,3)
使用扩展函数:
list.exchange(1,2)
使用扩展属性:
val isEven = list.sumIsEven
提示: MutableList 是接收者类型,list 是接收者对象。
在扩展函数内部,你可以像 「成员函数」 那样使用this来引用接受者对象,当然有时也可以省略,例如:
声明扩展属性:
val MutableList<Int>.sumIsEven
get() = this.sum() % 2 == 0 // 省略了 this.sum() 中的 this
可空接收者
在 第 2.1 节 中使用了「非空的接收者类型」来定义扩展(MutableList 没有关键词?),当使用「可空变量」调用扩展时,会报编译时错误。例如:
val list:MutableList<Int>? = null
list.sumIsEven // Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type MutableList<Int>?
根据提示,我们知道可以 使用「可空的接收者类型」来定义扩展,同时还要在内部使用null == this来对接收者对象进行判空。例如:
可空接收者类型的扩展函数
fun <T : Any?> MutableList<T>?.exchange(fromIndex: Int, toIndex: Int) {
if (null == this) return
val temp = this[fromIndex]
this[fromIndex] = this[toIndex]
this[toIndex] = temp
}
可空接收者类型的扩展属性
val MutableList<Int>?.sumIsEven: Boolean
get() = if (null == this)
false
else
this.sum() % 2 == 0
在 Java 中调用
扩展的本质:扩展函数是定义在类外部的静态函数,函数的第一个参数是接收者类型的对象。这意味着调用扩展时不会创建适配对象或者任何运行时的额外消耗。
在 Java 中,我们只需要像调用普通静态方法那样调用扩展即可。例如:
xxx.java
ArrayList<Integer> list = new ArrayList<>(3);
使用扩展函数:
UtilsKt.exchange(list, 1, 2);
使用扩展属性:
boolean isEven = UtilsKt.getSumIsEven(list);
扩展的作用域
当你定义了一个扩展之后,它不会自动在整个项目内生效。在其它包路径下,需要使用improt导入。例如:
import Utils.exchange
或
import Utils.*
当你在不同包中定义了 「重名扩展」,并且需要在同一个文件中去使用它们,那么你需要使用as关键字重新命名。例如:
import Utils.exchange as swap
使用时:
list.swap(0,1)