今天,让我们聊聊Kotlin中的扩展函数和属性的话题。
扩展函数和属性,见名知意,就是可以做到在目前已存在的类的基础上添加函数和属性,只是这些函数和属性定义在类的外部,是不是很好奇呢?那就一起来看看吧。
我们直接来一个例子进行分析,给String类添加一个成员函数lastChar
,用来获取字符串的最后一个字符。先上代码。
Kotlin代码
package expand
fun String.lastChar(): Char = this.get(this.length - 1)
对,你没有看错,除了包声明,就只有一行代码,它的定义规则是这样的。fun 需要扩展的类名.扩展的方法名: 扩展方法返回值 = 方法具体实现
。这里的this
表示一个要扩展的类的对象,在这里就是String对象,所以它可以调用String类的所有可以访问的属性和方法。
接着我们就可以像调用一般的方法一样调用我们的扩展方法了,没有任何差异,就像下面这样。
Kotlin中调用
import expand.lastChar
//注意这是在另一个Kotlin文件中的调用代码
fun main(args: Array) {
println("Kotlin".lastChar())
}
在使用前必须先导入函数,然后使用字符串.扩展函数
调用。
Java中调用
import expand.ExpandFunKt;
public class Main {
public static void main(String[] args) {
System.out.println(ExpandFunKt.lastChar("Kotlin"));
}
}
在Java中同样需要导入,只是导入的不是函数,而是以文件名创建的类名,然后像静态方法调用一般调用我们的扩展函数,将字符串作为参数传入即可。
当然Kotlin是一门追求简介的语言,上面的扩展函数还可以省略this
,就像下面这样。
fun String.lastChar(): Char = get(length - 1)
但请你注意,我们的扩展函数不允许破坏类的封装性,也就是我们在扩展时不能访问到类的私有属性和受保护的属性。
有时候,我们在Kotlin中导入的函数可能会重名,这时我们就可以使用as
关键字在导入的同时为其其一个别名,调用的时候使用这个别名即可,就像下面这样。
import expand.lastChar as last
fun main(args: Array){
println("Kotlin".last())
}
其实你要是完全理解了上面的例子,就可以瞬间理解为什么扩展函数不可以被重写。我们看看扩展函数在Java中的调用形式,其实就是调用了一个类的静态方法,这里就涉及到一个Java的知识点,静态函数不具备多态性,静态函数不可被重写
。我们写一个Java的例子来说明以下。
Java代码
//父类
public class Father {
public void say(){
System.out.println("我是爸爸。。。");
}
}
//子类继承父类
public class Son extends Father{
public void say(){
System.out.println("我是儿子。。。");
}
}
然后我们用下面的代码测试以下
public class Main {
public static void main(String[] args) {
Father father = new Father();
father.say();
Son son = new Son();
son.say();
Father obj = new Son();
obj.say();
}
}
输出如下:
我是爸爸。。。
我是儿子。。。
我是儿子。。。
这是Java的基础知识了,大家没什么异议吧,那我们接着吧上面的say()
方法修改成static
的看看,就像下面这样。
public class Father {
public static void say(){
System.out.println("我是爸爸。。。");
}
}
public class Son extends Father{
public static void say(){
System.out.println("我是儿子。。。");
}
}
测试代码
public class Main {
public static void main(String[] args) {
Father father = new Father();
father.say();
Son son = new Son();
son.say();
Father obj = new Son();
obj.say();
}
}
首先上面的测试代码不会运行出错,可能我们平时不会这样去调用静态方法,这里只是为了说明问题。
运行结果如下
我是爸爸。。。
我是儿子。。。
我是爸爸。。。
尤其看第三个运行结果,对,这就说明了静态方法不具有多态性
。如果再深入说以下就是普通成员变量的重写,导致的多态性是由于我们在使用运行时类型去调用我们重写的方法,而静态方法的调用只是看这个对象的静态类型
。
上面的规则在Kotlin中同样适用,只是变成了扩展函数不具有多态性(其实你只要记住扩展函数在Java中调用时是被作为静态函数处理的你就能理解了)
下面我们再用Kotlin来重现以下上面的场景。
Kotlin代码
package kt
//父类
open class Father{
open fun say(){
println("我是爸爸。。。")
}
}
package kt
//子类
class Son: Father() {
override fun say() {
println("我是儿子。。。")
}
}
上面的代码涉及到一点新的语法,首先在Kotlin中所有类默认是final
的,是不可继承的,必须在前面添加open
修饰符才可以被继承。其次继承不再使用extends
关键字,而是使用:
,同时,后面的继承类不是写类名,还要写()
,其实这里面是一体的,这个表示构造函数,这个我们后面的内容会详细介绍。最后注意方法的重写必须在函数前面写override
关键字。
测试代码
package kt
fun main(args: Array){
val father1 = Father();
father1.say();
val son = Son();
son.say();
val obj: Father = Son();
obj.say();
}
输出结果
我是爸爸。。。
我是儿子。。。
我是儿子。。。
现在我们开始扩展这两个类,给这两个类都扩展一个函数sayName()
,就像下面这样。
fun Father.sayName() = println("Father")
fun Son.sayName() = println("Son")
下面我们来测试一下扩展函数是否被重写了,也就是是否具备多态性。
val obj2: Father = Son()
obj2.sayName()
输出结果
Father
通过上面有点啰嗦的步骤,我们已经验证了扩展函数不可以被重写。
最后再说一点,如果一个类的成员函数和扩展函数具有相同的方法签名(也就是方法声明一致),那么成员函数会被优先使用。
学会了扩展函数,那么扩展属性学起来就容易一些了。我们仍然说说文章最开始的那个场景,我们为String类定义一个扩展属性lastChar
,就像下面这样。
val String.lastChar: Char get() = get(length - 1)
由于String是不可变的,所以我们这里扩展属性声明为val
,同时由于我们扩展的属性lastChar
并不能真正在类内部,所以我们没法给其赋初值和初始化,因为没有地方存储值,我们只能通过给它添加getter方法让其在被调用时返回值。
当然如果是为StringBuilder扩展属性我们就可以将扩展的属性声明为var
,并为其添加getter和setter方法,就像下面这样。
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char){
this.setCharAt(length - 1, value)
}
你可能刚开始对这种写法有些陌生,没事,后续我们将介绍更多相关的内容。
下面看看在Kotlin和Java中如何调用吧。
//Kotlin中调用
import expand.lastChar
fun main(args: Array<String>){
val sb = StringBuilder("abc")
sb.lastChar = 'M'
println(sb)
println(sb.lastChar)
}
使用前肯定要先导入,然后就可以像使用普通属性一样使用就可以啦。
//Java中调用
import expand.ExpandFunKt;
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("BNM");
ExpandFunKt.setLastChar(sb, 'W');
System.out.println(ExpandFunKt.getLastChar(sb));
}
}
在Java中调用时,则是将写有扩展属性代码的类导入,然后在传参数时需要传入扩展的类的对象和要设置的值,像静态函数一样调用他们的geteer和setter方法。
Kotlin的扩展函数和属性,增加代码设计的灵活性,我们可以在现有类的基础上进行扩展和修改,定制我们自己的类,这也极大地方便了Kotlin的Java的互操作。