Kotlin 学习之多态与扩展

186 阅读3分钟

如果你看过任何 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 + ba.plus(b)a == ba.equals(b)
a - ba.minus(b)a > b
a * ba.times(b)a < ba.compareTo(b)
a / ba.div(b)a >= b
a % ba.rem(b)a <= b
a++a.inc()a..ba.rangeTo(b)
a--a.dec()a[b]a.get(b)
+aa.unaryPlus()a[b] = ca.set(b, c)
-aa.unaryMinus()a in bb.contains(a)
!aa.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

关于扩展需要注意的几种情况

  1. 如果需要声明一个静态的扩展函数,必须将其定义在伴生对象(companion object)上。
  2. 当扩展函数和现有类的成员方法同时存在时,将会默认使用类的成员方法,即同名的类成员方法的优先级总是高于扩展函数。