Kotlin 扩展函数
在 android 开发中,经常会使用到各种 utils 工具类。比如 ViewUtils, ScreenUtils, FileUtils 等,
这些类往往会提供一些静态方法,让我们进行一些通用的操作。
或者,我们常常会希望给某一个类增加一两个小属性或者方法,在用到这个类的地方都能调用,方便我们的开发。比如,我们希望每个 String 对象都能有一个方法,返回这个 String 对象包含的大写字母个数。
我们可能会在某个 utils 中这样写
public static int getStringCapitalCount(String s) throws Exception {
if (s == null) {
throw new Exception("string should not be null!");
}
int count = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= 65 && c <= 90) {
count++;
}
}
return count;
}
或者, 我们会考虑使用一个子类来继承这个父类,然后给子类添加这个方法,在代码中使用子类。当然,String 是不可继承的。
这样写,并没有什么问题,一贯的 java 写法。
但是在 Kotlin 中,我们有了更优雅的方式。既然我们最开始的目标是希望 String 类型多一个方法,那么我们的代码也应该写得像这样,如下:
// 函数名以类名作为前缀: ClassName.funName
// 该前缀叫做 接收者类型(receiver type)
fun String.capitalCount(): Int{
var count = 0
this.forEach { // this 指的是类的实例对象, 叫 接收者对象(receiver object)
if (it.toInt() in 65..90) {
count ++
}
}
return count
}
然后,我们可以在任何使用到 String 的地方,调用这个方法,就好像 String 类型本来就拥有这个方法一样。
fun main() {
val s = "AbCdEfg"
println(s.capitalCount())
}
这就是 Kotlin 中的扩展函数。
一般,将扩展函数定义在一个 Extensions.kt 的文件中,最外层直接写函数就可以,不需要像 java 一样定义一个类。这样定义的函数是顶层函数,类似于 java 中的静态函数,可以在任何地方被调用。
类似地, 我们再写一个例子,比如经常用到的将 Date 转为 String 的函数,我们使用扩展函数的方式:
fun Date.dateString(): String{
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return dateFormat.format(this)
}
// 调用时
fun main() {
println(Date().dateString())
}
注意
-
扩展函数虽然看起来像是往类里面添加了成员函数,但是实际上并没有改变类的任何结构。所以,对于类内部 private、protected 以及 internal(模块外访问)修饰的成员变量和函数,是不能够访问到的,编译器会报错。
如果扩展函数和类内部定义的成员函数重名,那么调用时执行的是类的函数。
扩展是静态解析的,调⽤的扩展函数是由函数调⽤所在的表达式的类型来决定的,⽽不是由表达式运⾏时求值结果决定的。例如:
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
这个例子会输出 “Shape” ,因为调用的扩展函数只取决于 s 参数的类型声明,这里它是 Shape 类。
Kotlin 扩展属性
和扩展函数类似,Kotlin 同样支持扩展属性。比如:
// 给 StringBuilder 类添加扩展属性 firstLetter
var StringBuilder.firstLetter: Char
get() = get(0)
set(value) = this.setCharAt(0, value)
可以把扩展属性看作是两个扩展函数,一个是 getter, 一个是 setter 。
调用时:
fun main(args: Array<String>) {
val message = StringBuilder("hello !")
println("${message.firstLetter} is the first letter of $message")
message.firstLetter = 'H'
println("${message.firstLetter} is the first letter of $message")
}
输出结果:
h is the first letter of hello !
H is the first letter of Hello !
注意
扩展属性没有初始化器,即扩展属性不能初始化,只能使用 getter / setter 来操作扩展属性的值。
val StringBuilder.firsetLetter = 'a' // 这是错误的, 不能初始化扩展属性
这是因为扩展属性并没有真正地插入一个属性到类中,所以扩展属性没有幕后字段(backing field),不能进行初始化。同样,也不能在扩展属性的 setter 里使用 field 关键字。
配合泛型使用
可以使用泛型来给多个类添加扩展属性。比如:
// 得到逆序的 string
val <T> T.reverseString: String
get() {
return this.toString().reversed() // 实际上 reversed 函数也是 Kotlin String 类型自带的扩展函数
}
扩展的 Java 代码
Kotlin 究竟是如何实现扩展函数和扩展属性的呢?
还是使用之前 String.capitalCount 的例子,反编译后的 java 代码如下:
// Extension.kt 文件会被编译成 ExtensionKt.class
public final class ExtensionsKt {
public static final int capitalCount(@NotNull String $this$capitalCount) {
int count = 0;
CharSequence $this$forEach$iv = (CharSequence)$this$capitalCount;
int $i$f$forEach = false;
CharSequence var4 = $this$forEach$iv;
for(int var5 = 0; var5 < var4.length(); ++var5) {
char element$iv = var4.charAt(var5);
int var8 = false;
if ('A' <= element$iv) {
if ('Z' >= element$iv) {
++count;
}
}
}
return count;
}
实际上和在 java util 中实现方法一致,生成了一个以 String 类型为形参的静态方法,而函数形参 this$capitalCount 其实就是在 kotlin 中 this 指代的那个对象。
再看 StringBuilder.firstLetter 属性的反编译 java 代码:
public static final char getFirstLetter(@NotNull StringBuilder $this$firstLetter) {
return $this$firstLetter.charAt(0);
}
public static final void setFirstLetter(@NotNull StringBuilder $this$firstLetter, char value) {
$this$firstLetter.setCharAt(0, value);
}
其实就是生成了两个静态的方法,一个 setter 和 一个 getter。用两个扩展函数也可以达到同样的效果。