Kotlin系列之扩展函数和属性

今天,让我们聊聊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<String>){
    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<String>){
    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<String>){
    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的互操作。

阅读更多

扫码向博主提问

codekongs

夫唯不争,故莫能与之争。
  • 擅长领域:
  • Android
  • Java
  • Kotlin
  • 情感
  • 心理学
去开通我的Chat快问
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bingjianIT/article/details/79156450
文章标签: Kotlin
个人分类: Kotlin
上一篇Kotlin系列之顶层函数和属性
下一篇Kotlin系列之接口
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭