草庐IT

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

Stars-one 2023-03-28 原文

原文地址: 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
    }

}

扩展方法重载问题

由于是声明方法,可能会出现方法名重名的情况,即我们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代码

参考

有关Kotlin学习快速入门(7)——扩展的妙用的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. c - mkmf 在编译 C 扩展时忽略子文件夹中的文件 - 2

    我想这样组织C源代码:+/||___+ext||||___+native_extension||||___+lib||||||___(Sourcefilesarekeptinhere-maycontainsub-folders)||||___native_extension.c||___native_extension.h||___extconf.rb||___+lib||||___(Rubysourcecode)||___Rakefile我无法使此设置与mkmf一起正常工作。native_extension/lib中的文件(包含在native_extension.c中)将被完全忽略。

  3. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  4. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  5. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  6. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  7. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  8. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  9. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

  10. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

随机推荐