Kotlin-扩展

Posted bug樱樱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin-扩展相关的知识,希望对你有一定的参考价值。

一、扩展函数

1、扩展函数

扩展函数,就是从类的外部扩展出来的一个函数,这个函数看起来就像是类的成员方法一样。比如扩展一个String的方法。我们经常需要切割字符串然后获取第几个元素,我们扩展一个类似的工具类方法,如下:

fun String.getStringAfterCut(regular: String, index: Int): String? 
    split(regular).let  list ->
        return list.getOrNull(index)
    

复制代码

调用

"a.5.c".getStringAfterCut(".",1)

扩展函数中扩展的对象String在函数中被成为函数的接收者

2、扩展函数的原理

我们将扩展函数和调用的方法都反编译,会得到下面的结果:

//扩展函数反编译
//省略了几行不影响分析的代码
public final class StringKtKt 
   @Nullable
   public static final String getStringAfterCut(@NotNull String $this$getStringAfterCut, @NotNull String regular, int index) 
    //...
      List var3 = StringsKt.split$default((CharSequence)$this$getStringAfterCut, new String[]regular, false, 0, 6, (Object)null);
    //...
      return (String)CollectionsKt.getOrNull(var3, index);
   

//调用处的反编译
StringKtKt.getStringAfterCut("a.5.c", ".", 1);

可以看到扩展函数并没有成为String类的成员方法,仅仅是生成了StringKtKt类的静态方法,而扩展的对象被当做参数传入进了这个静态方法中。

二、扩展属性

1、扩展属性

扩展属性,是在类的外部为它定义一个新的成员属性。例如下面的方法,获取字符串的第一个字符:

val String.firstChar: Char?
    get() = if (isEmpty()) 
        null
     else 
        get(0)
    

调用

"a.5.c".firstChar

2、扩展属性的原理

反编译上面的代码看看结果:

//扩展属性反编译代码
//省略了几行不影响分析的代码
public final class StringKtKt 
   @Nullable
   public static final Character getFirstChar(@NotNull String $this$firstChar) 
      //...
      return var1.length() == 0 ? null : $this$firstChar.charAt(0);
   

//调用处的反编译
StringKtKt.getFirstChar("a.5.c");

可以看到和扩展函数类似,扩展属性并没有成为String类的成员属性,仅仅是生成了StringKtKt类的静态方法,而扩展的对象被当做参数传入进了这个静态方法中。

三、扩展函数和扩展属性应该如何选择

有些扩展可以写成扩展函数也可以写成扩展属性,那么应该如何选择? 如果语义上适合作为属性那么写成扩展属性比较合适,比如firstChar,语义上适合作为扩展函数则写成扩展函数。

四、扩展的访问域

扩展的访问域只讨论扩展函数,扩展属性类似。

顶层扩展:扩展的访问域仅限于该Kotlin文件当中的所有成员,以及被扩展类型的公开成员,这种方式定义的扩展是可以被全局使用的。

类内扩展:如果扩展函数在一个类里面,那么它的访问域是怎么样的呢?看如下代码:

class UploadManager private constructor() 
    //单例
    companion object 
        private var INSTANCE: UploadManager? = null
        fun getInstance(): UploadManager =
            INSTANCE ?: synchronized(this) 
                INSTANCE ?: UploadManager().also  INSTANCE = it 
            
    

    //扩展方法
    fun String.getStringAfterCut(regular: String, index: Int): String? 
        split(regular).let  list ->
            return list.getOrNull(index)
        
    

    //类的成员方法
    fun getFileName(filePath: String): String 
        return filePath.getStringAfterCut("/", 0) ?: ""
    


反编译上面的代码,仅仅看扩展函数的部分:

public final class UploadManager 
   private static UploadManager INSTANCE;
   @NotNull
   public static final UploadManager.Companion Companion = new UploadManager.Companion((DefaultConstructorMarker)null);

   @Nullable
   public final String getStringAfterCut(@NotNull String $this$getStringAfterCut, @NotNull String regular, int index) 
      //...
      List var4 = StringsKt.split$default((CharSequence)$this$getStringAfterCut, new String[]regular, false, 0, 6, (Object)null);
      //...
      return (String)CollectionsKt.getOrNull(var4, index);
   
   //...
 

可以看到扩展函数变成了UploadManager类的成员方法,而不是扩展的String了,因为限制了扩展函数的接受者为String类型,而方法的调用者只能是UploadManager的单例对象,所以该扩展函数无法被外部调用,只能在UploadManager类中被其他成员方法调用。

故类内扩展的访问域仅限于该类当中的所有成员,以及被扩展类型的公开成员,这种方式定义的扩展仅能在该类当中使用。

五、扩展的限制

除了匿名类没有具体的接收类型,其他类都可以被扩展,包括普通类、单例类、密封类、枚举类等等。

扩展的主要用途是取代Java中的各种工具类。

1、Kotlin 扩展不是真正的类成员,因此它无法被它的子类重写。
2、Kotlin 扩展属性无法存储状态。
3、扩展的访问作用域仅限于定义处的成员和接收者类型的公开成员。

六、扩展的实战运用

1、关注点分离

我们看kotlin包中String.kt的代码:

package kotlin

public class String : Comparable<String>, CharSequence 
    companion object 

    public operator fun plus(other: Any?): String

    public override val length: Int

    public override fun get(index: Int): Char

    public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence

    public override fun compareTo(other: String): Int

非常简单,只有String.kt类实现ComparableCharSequence重写的几个方法,那么我们平时使用的trimisEmpty等工具方法都去哪里了呢? 其实关于String的工具方法全都通过扩展的形式分离到了Strings.kt类中,这就是关注点分离,String.kt类只关注String自身,而扩展的工具方法全部由Strings.kt统一管理。

@file:kotlin.jvm.JvmMultifileClass
@file:kotlin.jvm.JvmName("StringsKt")

package kotlin.text

import kotlin.contracts.contract
import kotlin.jvm.JvmName

/**
 * Returns a copy of this string converted to upper case using the rules of the default locale.
 */
@Deprecated("Use uppercase() instead.", ReplaceWith("uppercase()"))
@DeprecatedSinceKotlin(warningSince = "1.5")
public expect fun String.toUpperCase(): String

/**
 * Returns a copy of this string converted to upper case using Unicode mapping rules of the invariant locale.
 *
 * This function supports one-to-many and many-to-one character mapping,
 * thus the length of the returned string can be different from the length of the original string.
 *
 * @sample samples.text.Strings.uppercase
 */
@SinceKotlin("1.5")
@WasExperimental(ExperimentalStdlibApi::class)
public expect fun String.uppercase(): String

/**
 * Returns a copy of this string converted to lower case using the rules of the default locale.
 */
@Deprecated("Use lowercase() instead.", ReplaceWith("lowercase()"))
@DeprecatedSinceKotlin(warningSince = "1.5")
public expect fun String.toLowerCase(): String

//...略...

2、提升可读性和开发效率

android开发中经常需要dp转px,如果写成工具类,那么就会这样使用:

//工具类调用
ConvertUtils.dp2px(15f)

但如果写成扩展属性的形式,那么调用就会变成

/**
 * 扩展属性:dp转化为px,返回Float值
 */
val Float.dp2px
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        this,
        Resources.getSystem().displayMetrics
    )
//调用
8F.dp2px

会变得简单直观很多,且全局可调用,大大提高了可读性和开发效率。

个人学习笔记

参考了以下内容

06 | 扩展:你的能力边界到底在哪里?

作者:TimeFine
链接:https://juejin.cn/post/7193964032729022520

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。


相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

以上是关于Kotlin-扩展的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 属性:“属性的类型参数必须在其接收者类型中使用”

Kotlin带接收者的lambda表达式

Kotlin Vocabulary | 使用 Kotlin 中的扩展提升代码可读性

Kotlin进阶系列-函数类型及函数字面值

Kotlin Vocabulary | 使用 Kotlin 中的扩展提升代码可读性

设计模式之命令模式