7

Kotlin学习快速入门(7)——扩展的妙用 - Stars-one

 1 year ago
source link: https://www.cnblogs.com/stars-one/p/16440099.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kotlin学习快速入门(7)——扩展的妙用

本文为作者原创,转载请注明出处,谢谢配合
作者:stars-one
链接:https://www.cnblogs.com/stars-one/p/16440099.html

本篇大约有4105个字,阅读预计需要5.13分钟


原文地址: Kotlin学习快速入门(7)——扩展的妙用 - Stars-One的杂货小窝

之前也模模糊糊地在用这个功能,也是十分方便,可以不用继承,快速给某个类增加新的方法,本篇便是来讲解下Kotlin中扩展这一概念的使用

先解释一下,扩展的说明,官方文档上解释:

Kotlin 能够扩展一个类的新功能,而无需继承该类或者使用像装饰者这样的设计模式

简单来说,就是可以不用继承来让一个类多出一个方法或属性(成员变量),可能这样说比较抽象,我们以一个简单的例子来说

比如说,我们需要用到以下功能:

判断String对象是否其是否为null或未空白字符串,如果为null或空白字符串,则返回true,否则返回false

此功能挺好实现,但我们想要实现此功能,无非就是3种方法:

  1. 写个工具类StringUtil,然后传递有个String对象进去,方法返回
  2. 写个新的类,让其继承于String类,之后再新增方法
  3. 用装饰者模式,扩展类(这里不多解释装饰者模式,可以自己百度查阅下资料)

但上面的方法,估计第一种各位都明白,也是十分简单,但使用起来还是比较麻烦,还得将对象作为入参传递,如果使用Kotlin的扩展特性,还能变得更加简单

而剩下两种,改动均是较大,一般得看情况使用,也是不太推荐

我们以刚才上述说的功能为例,实现判断String对象是否其是否为null或未空白字符串,如果为null或空白字符串,则返回true,否则返回false此功能

语法及使用

首先,显示讲解下语法

fun 类名.方法名(参数列表...):返回值{
    
}

看起来稍微有些抽象,我们直接上示例:

fun String.isBlankOrNullString(): Boolean {
    return this == null || this.trim().length == 0
}

需要注意的是,方法里的this就是当前调用此方法的String对象

扩展方法使用:

fun main(args: Array<String>) {
    val str = ""
    println(str.isBlankOrNullString())
}

PS: 这里的扩展方法写在了顶层,是全局可用的

  1. 扩展方法会区分作用域(全局和局部)
  2. 类中存在于扩展方法同名同参数列表,相当于重载,此时以扩展方法为主
  3. 扩展方法可接收可空类型

扩展方法作用域

扩展方法的声明位置,会决定此扩展方法的作用域

如下面示例:

fun main() {
    val str = ""
    println(str.isBlankOrNullString())
}

class User {
    val str = ""
}

fun String.isBlankOrNullString(): Boolean {
    return this == null || this.trim().length == 0
}

我们将方法写在了最外层(即与class关键字同级),此时,我们可以在任意的类中调用此方法

但如果我们稍微改一下,如下:

fun main() {
    val str = ""
    //这里会报错!!
    //println(str.isBlankOrNullString())
}

class User {
    val str = ""

    fun sayHello() {
        //类中可以正常使用
        str.isBlankOrNullString()
    }

    fun String.isBlankOrNullString(): Boolean {
        return this == null || this.trim().length == 0
    }

}
1210268-20220703144127415-581068778.png

扩展方法重载问题

由于是声明方法,可能会出现方法名重名的情况,即我们Java基础中说到的重载关系

这里,如果出现了重载的情况(方法名和参数列表相同),会以类中的方法为主(即会忽略掉扩展方法)

上面此句,是根据文档上总结得来的,实际上也是测试通过

fun main() {
    Example().printFunctionType()
}

class Example {
    fun printFunctionType() { println("Class method") }
}

fun Example.printFunctionType() { println("Extension function") }

最后输出的是Class method

但这里有个奇怪的情况,我以String的扩展为例,测试发现与上述结论不一致!!

以下是我的测试代码:

fun main() {
    val str = ""
    println(str.isNullOrBlank())
}

fun String.isNullOrBlank(): Boolean {
    println("进入我们的方法里")
    return this == null || this.trim().length == 0
}

最终输出:

进入我们的方法里
true

看着打印,这明显就是进到我们定义的扩展方法里啊 😕

研究了一番,发现原本的那个isNullOrBlank(),并不是String类中含有的方法

官方也是通过扩展方法来实现追加的,且是扩展的类是CharSequence,而且此类是个接口类,所有实现了此接口的类都有了isNullOrBlank()方法

而我们自己也是定义了个扩展方法,与官方的扩展方法发生了重载,于是我们的扩展方法便是把官方的扩展方法覆盖了

所以得出以下结论:

当类中存在某个方法,扩展方法与此方法发生重载关系,会以类中方法为主

某类已存在某个扩展方法,用户自定义扩展方法与该扩展方法发生重载,会以用户自定义扩展方法为主

当然,上面这是自己研究定下的,若是不太准确,希望各位可以指正! 😄

扩展方法接收可空类型

上面,我们是定义的String类型扩展,当然,也可以给String?可空类型进行扩展方法

这样写也是没有问题的:

fun String?.isNullOrBlank(): Boolean {
    println("进入我们的方法里")
    return this == null || this.trim().length == 0
}

像这样,我们还可以直接给所有类型(Any类型)增加个toString()方法的扩展方法:

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}

但是,这个扩展方法是不起作用的!!

为什么呢?因为Any对象已经存在了toString()此方法,根据上面的结论,会以类中的方法优先!

除了方法,我们也可以实现扩展属性

val 类型.属性名: 属性类型名
    get() = 

如有个示例,判断文件是否为md文件:

val File.isMdFile: Boolean
    get() = extension.toLowerCase()=="md"
fun main() {
    val file =File("D:\\tt.md")
    println(file.isMdFile)
}

val File.isMdFile: Boolean
    get() = extension.toLowerCase()=="md"

相关作用域与上述扩展方法讲解的是一致的,这里不再赘述

扩展伴生对象

这里,感觉就是类似工具类的扩展吧,如果使用伴生对象,之后就可以类名.方法名去调用方法(类似Java中的静态方法)

如果我们想追加一些方法,也可以使用扩展来实现,如下例子

class MyClass {
    companion object { }  // 将被称为 "Companion"
}

fun MyClass.Companion.printCompanion() { println("companion") }

fun main() {
    MyClass.printCompanion()
}

Kotlin中的扩展函数,其实最后编译成class文件都会转为一个静态方法

这一过程实际上是由Kotlin编译器替我们实现了,我们只管吃语法糖就完事了!

我们以下面方法为例:

fun String?.isNullOrBlank(): Boolean {
    println("进入我们的方法里")
    return this == null || this.trim().length == 0
}

最终生成的静态方法:

// 这个类名就是顶层文件名+“Kt”后缀
public final class ExtendsionDemoKt {
   // 扩展函数 isNullOrBlank 对应实际上是 Java 中的静态函数,并且传入一个接收者类型对象作为参数
   public static final boolean isNullOrBlank(@NotNull CharSequence $this$isNullOrBlank) {
      Intrinsics.checkParameterIsNotNull($this$isNullOrBlank, "$this$isNullOrBlank");
      String var1 = "进入我们的方法";
      boolean var2 = false;
      System.out.println(var1);
      return StringsKt.trim($this$isNullOrBlank).length() == 0;
   }
}

如果我们isNullOrBlank还有参数的话,静态方法中除了CharSequence这个参数,还会多出其他参数

PS: 可以点开对应的class文件,然后使用tool->kotlin->Decompile Kotlin To Java,将class还原会java代码

1210268-20220703163433218-1377353947.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK