如果你看过任何 KotlinConf 演讲,可能经常看到,扩展函数被称为 Kotlin 最有魅力的特性之一。Kotlin 的扩展其实是多态的一种表现形式,在深入了解扩展之前,我们先探究一下多态的不同技术手段。
多态的不同方式
1、子类型多态
当我们用一个子类继承一个父类的时候,子类可以使用父类的所有方法,这种用子类型替换父类型实例的行为,就是子类型多态。
2、参数多态
参数多态最常见的形式是泛型。参数多态是指声明与定义函数、复合类型、变量时不指定其具体的类型,而把这部分类型作为参数使用,使得该定义对各种具体类型都适用,它是建立在运行时的参数基础上,并且在不影响类型安全的前提下进行。
3、特设多态
特设多态可以理解为一个多态函数是有多个不同的实现,依赖于实参而调用相应版本的函数。这是一种更加灵活的多态技术,Kotlin 中的一些有趣的语言特性,如运算符重载,扩展都是很好的支持这种形式的多态。
运算符重载
运算符重载是 Kotlin 提供的一个比较有趣的语法糖。Kotlin 允许我们将所有的运算符甚至一些关键字进行重载,从而拓展这些运算符和关键字的用法。
运算符重载使用的是 operator 关键字。以加号运算符为例,想要实现两个对象的相加,代码如下:
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
}
重写了加号运算符,那么两个 Money 对象就可以相加了:
val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2
println(money3.value)
这种 money1 + money2 的语法看上去好像很神奇,但其实就是 Kotlin 提供的一种语法糖,它会在编译的时候被转换成 money1.plus(money2) 的调用方式。
这里只是介绍了加号运算符重载的用法,实际上 Kotlin 允许我们重载的运算符和关键字多达十几个。下面表中列举了常用的可重载运算符和关键字对应的语法糖表达式,以及它们会被转换成的实际调用函数。
语法糖表达式 | 实际调用函数 | 语法糖表达式 | 实际调用函数 |
---|---|---|---|
a + b | a.plus(b) | a == b | a.equals(b) |
a - b | a.minus(b) | a > b | |
a * b | a.times(b) | a < b | a.compareTo(b) |
a / b | a.div(b) | a >= b | |
a % b | a.rem(b) | a <= b | |
a++ | a.inc() | a..b | a.rangeTo(b) |
a-- | a.dec() | a[b] | a.get(b) |
+a | a.unaryPlus() | a[b] = c | a.set(b, c) |
-a | a.unaryMinus() | a in b | b.contains(a) |
!a | a.not() |
扩展
扩展函数
扩展函数表示即使在不修改某个类的源码的情况下,任然可以打开这个类,向该类添加新的函数。Kotlin 扩展函数这一特性遵循了开放封闭原则,即对扩展开放,而对修改封。
扩展函数的语法结构很简单,如下所示:
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
相比于定义一个普通的函数,定义扩展函数只需要在函数名的前面加上一个 ClassName. 的前缀,就表示将该函数添加到指定的类中了。
注意:扩展函数定义为一个顶层方法时,可以理解为 Java 方法中被 static 修饰了的静态方法,这样扩展函数拥有全局的访问域。当扩展函数定义在一个类的内部时,相当于一个没有被 static 修饰的 Java 方法,只能在该类和该类的子类中进行调用。
代码举例:
fun String.lettersCount(): Int {
var count = 0
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
上述代码向 String 类中添加了一个统计字母数量的扩展函数 lettersCount(),该函数自动拥有了 String 实例的上下文。因此 lettersCount() 函数就不再需要接收一个字符串参数了,而是直接遍历 this 即可,因为现在 this 就代表字符串本身。调用如下:
val count = "AFSsdf879".lettersCount()
扩展属性
与扩展函数类似,我们还能为一个类添加扩展属性。其本质也是对应 Java 中的静态方法。因此扩展数据不能为其初始化一个值,只能由显示提供的 getters 和 setters 定义。举例,给 MutableList<Int> 添加一个判断是否为偶数的扩展属性:
val MutableList<Int>.sumIsEven: Boolean
get() = this.sum() % 2 == 0
这样就可以和扩展函数一样调用:
val list= mutableListOf(1,2,3,4)
println(list.sumIsEven)//true
关于扩展需要注意的几种情况
- 如果需要声明一个静态的扩展函数,必须将其定义在伴生对象(companion object)上。
- 当扩展函数和现有类的成员方法同时存在时,将会默认使用类的成员方法,即同名的类成员方法的优先级总是高于扩展函数。