一、扩展函数

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重写的几个办法,那么咱们平常运用的trim isEmpty等东西办法都去哪里了呢? 其实关于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

会变得简略直观许多,且大局可调用,大大提高了可读性和开发功率。

个人学习笔记

参阅了以下内容

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