Kotlin系列学习——扩展函数和属性

布满荆棘的人生 2023-06-25 10:28 95阅读 0赞

今天,让我们聊聊Kotlin中的扩展函数和属性的话题。

扩展函数和属性,见名知意,就是可以做到在目前已存在的类的基础上添加函数和属性,只是这些函数和属性定义在类的外部,是不是很好奇呢?那就一起来看看吧。

扩展函数

我们直接来一个例子进行分析,给String类添加一个成员函数lastChar,用来获取字符串的最后一个字符。先上代码。

Kotlin代码

  1. package expand
  2. fun String.lastChar(): Char = this.get(this.length - 1)
  3. 1
  4. 2

对,你没有看错,除了包声明,就只有一行代码,它的定义规则是这样的。fun 需要扩展的类名.扩展的方法名: 扩展方法返回值 = 方法具体实现。这里的this表示一个要扩展的类的对象,在这里就是String对象,所以它可以调用String类的所有可以访问的属性和方法。
接着我们就可以像调用一般的方法一样调用我们的扩展方法了,没有任何差异,就像下面这样。

Kotlin中调用

  1. import expand.lastChar
  2. //注意这是在另一个Kotlin文件中的调用代码
  3. fun main(args: Array<String>){
  4. println("Kotlin".lastChar())
  5. }
  6. 1
  7. 2
  8. 3
  9. 4
  10. 5
  11. 6

在使用前必须先导入函数,然后使用字符串.扩展函数调用。

Java中调用

  1. import expand.ExpandFunKt;
  2. public class Main {
  3. public static void main(String[] args) {
  4. System.out.println(ExpandFunKt.lastChar("Kotlin"));
  5. }
  6. }
  7. 1
  8. 2
  9. 3
  10. 4
  11. 5
  12. 6
  13. 7

在Java中同样需要导入,只是导入的不是函数,而是以文件名创建的类名,然后像静态方法调用一般调用我们的扩展函数,将字符串作为参数传入即可。
当然Kotlin是一门追求简介的语言,上面的扩展函数还可以省略this,就像下面这样。

  1. fun String.lastChar(): Char = get(length - 1)
  2. 1

但请你注意,我们的扩展函数不允许破坏类的封装性,也就是我们在扩展时不能访问到类的私有属性和受保护的属性。
有时候,我们在Kotlin中导入的函数可能会重名,这时我们就可以使用as关键字在导入的同时为其其一个别名,调用的时候使用这个别名即可,就像下面这样。

  1. import expand.lastChar as last
  2. fun main(args: Array<String>){
  3. println("Kotlin".last())
  4. }
  5. 1
  6. 2
  7. 3
  8. 4
  9. 5

扩展函数不可被重写

其实你要是完全理解了上面的例子,就可以瞬间理解为什么扩展函数不可以被重写。我们看看扩展函数在Java中的调用形式,其实就是调用了一个类的静态方法,这里就涉及到一个Java的知识点,静态函数不具备多态性,静态函数不可被重写。我们写一个Java的例子来说明以下。

Java代码

  1. //父类
  2. public class Father {
  3. public void say(){
  4. System.out.println("我是爸爸。。。");
  5. }
  6. }
  7. //子类继承父类
  8. public class Son extends Father{
  9. public void say(){
  10. System.out.println("我是儿子。。。");
  11. }
  12. }
  13. 1
  14. 2
  15. 3
  16. 4
  17. 5
  18. 6
  19. 7
  20. 8
  21. 9
  22. 10
  23. 11
  24. 12
  25. 13

然后我们用下面的代码测试以下

  1. public class Main {
  2. public static void main(String[] args) {
  3. Father father = new Father();
  4. father.say();
  5. Son son = new Son();
  6. son.say();
  7. Father obj = new Son();
  8. obj.say();
  9. }
  10. }
  11. 1
  12. 2
  13. 3
  14. 4
  15. 5
  16. 6
  17. 7
  18. 8
  19. 9
  20. 10
  21. 11
  22. 12

输出如下:

  1. 我是爸爸。。。
  2. 我是儿子。。。
  3. 我是儿子。。。
  4. 1
  5. 2
  6. 3

这是Java的基础知识了,大家没什么异议吧,那我们接着吧上面的say()方法修改成static的看看,就像下面这样。

  1. public class Father {
  2. public static void say(){
  3. System.out.println("我是爸爸。。。");
  4. }
  5. }
  6. public class Son extends Father{
  7. public static void say(){
  8. System.out.println("我是儿子。。。");
  9. }
  10. }
  11. 1
  12. 2
  13. 3
  14. 4
  15. 5
  16. 6
  17. 7
  18. 8
  19. 9
  20. 10
  21. 11

测试代码

  1. public class Main {
  2. public static void main(String[] args) {
  3. Father father = new Father();
  4. father.say();
  5. Son son = new Son();
  6. son.say();
  7. Father obj = new Son();
  8. obj.say();
  9. }
  10. }
  11. 1
  12. 2
  13. 3
  14. 4
  15. 5
  16. 6
  17. 7
  18. 8
  19. 9
  20. 10
  21. 11
  22. 12

首先上面的测试代码不会运行出错,可能我们平时不会这样去调用静态方法,这里只是为了说明问题。
运行结果如下

  1. 我是爸爸。。。
  2. 我是儿子。。。
  3. 我是爸爸。。。
  4. 1
  5. 2
  6. 3

尤其看第三个运行结果,对,这就说明了静态方法不具有多态性。如果再深入说以下就是普通成员变量的重写,导致的多态性是由于我们在使用运行时类型去调用我们重写的方法,而静态方法的调用只是看这个对象的静态类型
上面的规则在Kotlin中同样适用,只是变成了扩展函数不具有多态性(其实你只要记住扩展函数在Java中调用时是被作为静态函数处理的你就能理解了)
下面我们再用Kotlin来重现以下上面的场景。

Kotlin代码

  1. package kt
  2. //父类
  3. open class Father{
  4. open fun say(){
  5. println("我是爸爸。。。")
  6. }
  7. }
  8. package kt
  9. //子类
  10. class Son: Father() {
  11. override fun say() {
  12. println("我是儿子。。。")
  13. }
  14. }
  15. 1
  16. 2
  17. 3
  18. 4
  19. 5
  20. 6
  21. 7
  22. 8
  23. 9
  24. 10
  25. 11
  26. 12
  27. 13
  28. 14
  29. 15
  30. 16
  31. 17

上面的代码涉及到一点新的语法,首先在Kotlin中所有类默认是final的,是不可继承的,必须在前面添加open修饰符才可以被继承。其次继承不再使用extends关键字,而是使用:,同时,后面的继承类不是写类名,还要写(),其实这里面是一体的,这个表示构造函数,这个我们后面的内容会详细介绍。最后注意方法的重写必须在函数前面写override关键字。
测试代码

  1. package kt
  2. fun main(args: Array<String>){
  3. val father1 = Father();
  4. father1.say();
  5. val son = Son();
  6. son.say();
  7. val obj: Father = Son();
  8. obj.say();
  9. }
  10. 1
  11. 2
  12. 3
  13. 4
  14. 5
  15. 6
  16. 7
  17. 8
  18. 9
  19. 10
  20. 11
  21. 12

输出结果

  1. 我是爸爸。。。
  2. 我是儿子。。。
  3. 我是儿子。。。
  4. 1
  5. 2
  6. 3

现在我们开始扩展这两个类,给这两个类都扩展一个函数sayName(),就像下面这样。

  1. fun Father.sayName() = println("Father")
  2. fun Son.sayName() = println("Son")
  3. 1
  4. 2
  5. 3

下面我们来测试一下扩展函数是否被重写了,也就是是否具备多态性。

  1. val obj2: Father = Son() obj2.sayName()
  2. 1
  3. 2

输出结果

  1. Father
  2. 1

通过上面有点啰嗦的步骤,我们已经验证了扩展函数不可以被重写。
最后再说一点,如果一个类的成员函数和扩展函数具有相同的方法签名(也就是方法声明一致),那么成员函数会被优先使用。

扩展属性

学会了扩展函数,那么扩展属性学起来就容易一些了。我们仍然说说文章最开始的那个场景,我们为String类定义一个扩展属性lastChar,就像下面这样。

  1. val String.lastChar: Char get() = get(length - 1)
  2. 1

由于String是不可变的,所以我们这里扩展属性声明为val,同时由于我们扩展的属性lastChar并不能真正在类内部,所以我们没法给其赋初值和初始化,因为没有地方存储值,我们只能通过给它添加getter方法让其在被调用时返回值。
当然如果是为StringBuilder扩展属性我们就可以将扩展的属性声明为var,并为其添加getter和setter方法,就像下面这样。

  1. var StringBuilder.lastChar: Char
  2. get() = get(length - 1)
  3. set(value: Char){
  4. this.setCharAt(length - 1, value)
  5. }
  6. 1
  7. 2
  8. 3
  9. 4
  10. 5

你可能刚开始对这种写法有些陌生,没事,后续我们将介绍更多相关的内容。
下面看看在Kotlin和Java中如何调用吧。

  1. //Kotlin中调用
  2. import expand.lastChar
  3. fun main(args: Array<String>){
  4. val sb = StringBuilder("abc")
  5. sb.lastChar = 'M'
  6. println(sb)
  7. println(sb.lastChar)
  8. }
  9. 1
  10. 2
  11. 3
  12. 4
  13. 5
  14. 6
  15. 7
  16. 8
  17. 9

使用前肯定要先导入,然后就可以像使用普通属性一样使用就可以啦。

  1. //Java中调用
  2. import expand.ExpandFunKt;
  3. public class Main {
  4. public static void main(String[] args) {
  5. StringBuilder sb = new StringBuilder("BNM");
  6. ExpandFunKt.setLastChar(sb, 'W');
  7. System.out.println(ExpandFunKt.getLastChar(sb));
  8. }
  9. }
  10. 1
  11. 2
  12. 3
  13. 4
  14. 5
  15. 6
  16. 7
  17. 8
  18. 9
  19. 10

在Java中调用时,则是将写有扩展属性代码的类导入,然后在传参数时需要传入扩展的类的对象和要设置的值,像静态函数一样调用他们的geteer和setter方法。

写在最后

Kotlin的扩展函数和属性,增加代码设计的灵活性,我们可以在现有类的基础上进行扩展和修改,定制我们自己的类,这也极大地方便了Kotlin的Java的互操作。

发表评论

表情:
评论列表 (有 0 条评论,95人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Kotlin系列扩展函数

    简述: 今天带来的是Kotlin浅谈系列的第五弹,这讲主要是讲利用Kotlin中的扩展函数特性让我们的代码变得更加简单和整洁。扩展函数是Kotlin语言中独有的新特性,利用它可