Kotlin 的扩展

Kotlin 的扩展

/*什么是扩展函数和扩展属性?*/

//  这个扩展函数是直接定义在 Kotlin 文件里的,而不是定义在某个类当中的。这种扩展函数,
//我们称之为“顶层扩展”,这么叫它是因为它并没有嵌套在任何的类当中,它自身就在最外层。
/*
 ①    ②      ③            ④
 ↓     ↓       ↓            ↓      */
fun String.lastElement(): Char? {
    //    ⑤
    //    ↓
    if (this.isEmpty()) {   //在整个扩展函数的方法体当中,this 都是可以省略的
        return null
    }

    return this[length - 1]
}
/*
* 注释①,fun关键字,代表我们要定义一个函数。也就是说,不管是定义普通 Kotlin 函数,还是定义扩展函数,我们都需要 fun 关键字。
* 注释②,“String.”,代表我们的扩展函数是为 String 这个类定义的。在 Kotlin 当中,它有一个名字,叫做接收者(Receiver),也就是扩展函数的接收方。
* 注释③,lastElement(),是我们定义的扩展函数的名称。
* 注释④,“Char?”,代表扩展函数的返回值是可能为空的 Char 类型。
* 注释⑤,“this.”,代表“具体的 String 对象”,当我们调用 msg.lastElement() 的时候,this 就代表了 msg。
*/

// 使用扩展函数
/*
fun main() {
    val msg = "Hello World"
    // lastElement就像String的成员方法一样可以直接调用
    val last = msg.lastElement() // last = d
    println(last)
}
*/

//如果我们在普通函数的名称前面加上一个“接收者类型”,
//比如“String.”,Kotlin 的“普通函数”就变成了“扩展函数”。

//fun lastElement(): Char? {}
//fun String.lastElement(): Char? {}
// 普通函数与扩展函数之间的差别


/*扩展函数的实现原理*/

//反编译后的 Java 代码
/*
public final class ExtKt {
    // ①
    public static final Character lastElement(String $this) {
        CharSequence var1 = (CharSequence)$this;
        if (var1.length() == 0) {
            return null
        }

        return  var1.charAt(var1.length() - 1);
    }
}

public static final void main() {
    String msg = "Hello World";
    //                        ②
    //                        ↓
    Character last = ExtKt.lastElement(msg);
}
*/
//  通过第一个注释,可以看到,原本定义在 String 类型上面的扩展函数 lastElement(),变成了一个普通的静态方法。
//另外,之前定义的扩展函数 lastElement() 是没有参数的,但反编译后的 Java 代码中,lastElement(String $this) 多了一个 String 类型的参数。
//  还有第二个注释,这是扩展函数的调用处,原本 msg.lastElement() 的地方,变成了 ExtKt.lastElement(msg)。
//这说明,Kotlin 编写的扩展函数调用代码,最终会变成静态方法的调用。

//由于 JVM 不理解 Kotlin 的扩展语法,所以 Kotlin 编译器会将扩展函数转换成对应的静态方法,而扩展函数调用处的代码也会被转换成静态方法的调用。


/*如何理解扩展属性?*/
//扩展函数,是在类的外部为它定义一个新的成员方法;而扩展属性,则是在类的外部为它定义一个新的成员属性。

// 接收者类型
//     ↓
val String.lastElement: Char?
    get() = if (isEmpty()) {
        null
    } else {
        get(length - 1)
    }

fun main() {
    val msg = "Hello Wolrd"
    // lastElement就像String的成员属性一样可以直接调用
    val last = msg.lastElement // last = d
}
/*
    在这段的代码中,我们为 String 类型扩展了一个新的成员属性“lastElement”。
  然后在 main 函数当中,我们直接通过“msg.lastElement”方式使用了这个扩展属性,
  就好像它是一个成员一样。
*/

/*
那么,到底什么时候该用扩展函数,什么时候该用扩展属性呢?
    其实只需要看扩展在语义上更适合作为函数还是属性就够了。比如这里的 lastElement,它更适合作为一个扩展属性。
这样设计的话,在语义上,lastElement 就像是 String 类当中的属性一样,它代表了字符串里的最后一个字符。
*/


/*扩展的能力边界*/

/*扩展能做什么?*/
//在 Kotlin 当中,几乎所有的类都可以被扩展,包括普通类、单例类、密封类、枚举类、伴生对象,甚至还包括第三方提供的 Java 类。唯有匿名内部类,由于它本身不存在名称,我们无法指定“接收者类型”,所以不能被扩展。
//Kotlin 扩展的应用范围还是非常广的。它最主要的用途,就是用来取代 Java 当中的各种工具类,比如 StringUtils、DateUtils 等等。
//扩展函数的优势在于,开发工具可以在编写代码的时候智能提示。

/*扩展不能做什么?*/
//  本质上并没有修改接收类型的源代码,所以它的行为是无法与“类成员”完全一致的。
//那么它对比普通的类成员,就会有以下几个限制:

/*第一个限制,Kotlin 扩展不是真正的类成员,因此它无法被它的子类重写*/
//举个例子,我们定义一个这样的 Person 类,并且分别为它扩展了一个 isAdult 属性和 walk() 方法:
open class Person {
    var name: String = ""
    var age: Int = 0
}

val Person.isAdult: Boolean
    get() = age >= 18

fun Person.walk() {
    println("walk")
}
/*
* 由于 Person 类有 open 关键字修饰,所以可以继承这个 Person 类。不过,
  当尝试去重写它的成员时,会发现 isAdult 和 walk() 是无法被重写的,因
  为它们压根就不属于 Person 这个类。
*/

/*第二个限制,扩展属性无法存储状态*/
//如前面代码当中的 isAdult 属性,它的值是由 age 这个成员属性决定的,它本身没有状态,也无法存储状态。

/*第三个限制,扩展的访问作用域仅限于两个地方。第一,定义处的成员;第二,接收者类型的公开成员*/
// ①
private val msg: String = ""

fun String.lastElement2(): Char? {
    if (this.isEmpty()) {
        //       ②
        //       ↓
        println(msg)
        return null
    }
    //            ③
    //            ↓
    return this[length - 1]
}
/*
* 在注释①的地方,我们在 Ext 这个 Kotlin 文件里定义了一个私有的变量 msg。
* 由于 lastElement() 与 msg 是定义在同一个文件当中的,因此,在注释②处我们可以直接访问 msg,即使它是私有的。
* 最后,是注释③,由于 length 是 String 类的公开属性,因此我们可以在扩展函数当中直接访问它。对应的,如果 length 是 String 的 private、protected 成员,那我们将无法在扩展函数当中访问它。归根结底,还是因为扩展函数并非真正的类成员。
*/

//如果将扩展定义在某个类的内部,它能够访问这个类的私有属性吗?
open class Person2 {
    var name: String = ""
    var age: Int = 0
}

class Helper {
    private fun walkOnFoot() {
        println("用脚走路")
    }
    val Person2.isAdult: Boolean
        get() = age >= 18

    fun Person2.walk() {
        // 调用了Helper的私有方法
        walkOnFoot()
    }

    fun test() {
        val person = Person2()
        // 仅可以在Helper类当中使用此扩展
        person.walk()
    }
}
//针对扩展的第三个限制来说:
/*
* 如果扩展是顶层的扩展,那么扩展的访问域仅限于该 Kotlin 文件当中的所有成员,以及被扩展类型的公开成员,
  这种方式定义的扩展是可以被全局使用的。
* 如果扩展是被定义在某个类当中的,那么该扩展的访问域仅限于该类当中的所有成员,以及被扩展类型的公开成员,
  这种方式定义的扩展仅能在该类当中使用。
*/


/*实战与思考*/

// String.kt
/*
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
}
*/

// Strings.kt 部分代码
/*
public fun CharSequence.trim(): CharSequence = trim(Char::isWhitespace)

public expect fun String.lowercase(): String
*/

//Kotlin 扩展的第一个典型使用场景:关注点分离。所谓关注点分离,就是将我们程序的逻辑划分成不同的部分,每一个部分,都只关注自己那部分的职责。
//以上面的 String 类为例,String.kt这个类,只关注 String 的核心逻辑;而Strings.kt则只关注 String 的操作符逻辑。

//Kotlin 扩展主要有两个核心使用场景
/*
* 主动使用扩展,通过它来优化软件架构。
  对复杂的类进行职责划分,关注点分离。让类的核心尽量简单易懂,而让类的功能性属性与方法以扩展的形式存在于类的外部。比如我们的String.kt与Strings.kt。
* 被动使用扩展,提升可读性与开发效率。
  当无法修改外部的 SDK 时,对于重复的代码模式,我们将其以扩展的方式封装起来,提供给对应的接收者类型,比如 view.updateMargin()。
*/

小结

  • Kotlin 的扩展,从语法角度来看,分为扩展函数和扩展属性。定义扩展的方式,只是比普通函数、属性多了一个“扩展接收者”而已。
  • 从作用域角度来看,分为顶层扩展和类内扩展。
  • 从本质上来看,扩展函数和扩展属性,它们都是 Java 静态方法,与 Java 当中的工具类别无二致。对比 Java 工具类,扩展最大的优势就在于,IDE 可以为我们提供代码补全功能。
  • 从能力的角度来看,Kotlin 扩展一共有三个限制,分别是:扩展无法被重写;扩展属性无法存储状态;扩展的作用域有限,无法访问私有成员。
  • 从使用场景的角度来看,Kotlin 扩展主要有两个使用场景,分别是:关注点分离,优化代码架构;消灭模板代码,提高可读性和开发效率。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值