美文网首页
Kotlin扩展

Kotlin扩展

作者: chym | 来源:发表于2020-09-07 16:40 被阅读0次

    扩展函数

    Kotlin中要扩展一个类的功能,除了使用继承(直接继承或继承一个接口使用委托)外,更便捷的方式是为该类定义扩展函数或扩展属性。此时称该类为接收者(Receiver),通常我们会把扩展(Extension)定义为顶层的,以便于在各处使用。

    fun Fragment?.isAlive(): Boolean {
        return this?.activity != null
                && this.activity?.isDestroyed == false
                && this.activity?.isFinishing == false
                && this.isAdded
                && !this.isDetached
    }
    

    扩展函数两种类型声明与转换

    当使用一个变量引用该扩展函数,通过IDE的自动补全,可以发现变量的类型为(Fragment) -> Boolean,可实际上,将类型声明为扩展更符合原义,即同一个函数可以声明为两种类型。

    val a: (Fragment) -> Boolean = Fragment::isAlive
    val b: Fragment.() -> Boolean = Fragment::isAlive
    

    不过这也说明,这两种类型其实在Kotlin认为是一致的,可以通过反编译得知两个变量的类型都为Function1。编译器所理解的扩展函数,实际上是将接收者作为其第一个参数的普通函数罢了。

    public interface Function1<in P1, out R> : Function<R> {
        public operator fun invoke(p1: P1): R
    }
    

    因此,通过将扩展函数引用(Receiver::method)赋予一个函数变量并指定类型,并用该变量调用函数,将扩展函数转化为普通函数的形式,反之亦然。

    而扩展函数引用,实际与类方法引用(Class::method)在语法上是完全一致的,类方法也支持通过这种方式进行调用形式的转换。

        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            Handler().postDelayed({
                print(isAlive()) //扩展函数
                print(a(this)) //通过扩展函数引用调用
                print(b()) //通过扩展函数引用调用
                
                val c: (Fragment) -> Boolean = Fragment::isAdded //类方法
                val d: Fragment.() -> Boolean = Fragment::isAdded
                print(c(this)) //通过类方法引用调用
                print(d()) //通过类方法引用调用
            }, 5000)
            ...
        }
    

    方法与函数

    从前,在过程式和函数式编程语言(C,JavaScript)中,我们似乎更愿意使用名称“函数”,面向对象的语言(Java)中我们更愿意使用名称“方法”,来区分将函数操纵的对象放在括号内还是括号前的不同书写方式,虽然现在通常并不区分两种名称的使用了。Java中,我们经常在各种Util类中定义静态方法,以“函数”的形式来扩充类的能力,而Kotlin的扩展函数,则提供了一种更面向对象的表达方式。

    扩展属性

    Kotlin中通过varval声明的属性(Property),要求必须进行初始化,这个初始化实际上是为其字段(backing field)赋值。属性除了字段外,还包括val的getter方法,var的getter和setter方法。

    而扩展属性并没有实际位于类中,它没有backing field,也不支持初始化。扩展属性虽然称为属性,原理上不过是定义了getter或setter扩展方法,然后通过属性名调用扩展方法而已。

    var File.content: String
        get() {
            return readText()
        }
        set(value) {
            writeText(value)
        }
    val Float.dp: Float
        get() {
            return TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_DIP, this, appContext.resources.displayMetrics)
        }
    

    适用于所有类型的扩展函数

    Kotlin标准库中定义4个适用于所有类型的扩展函数,声明如下:

    public inline fun <T, R> T.run(block: T.() -> R): R
    public inline fun <T, R> T.let(block: (T) -> R): R
    public inline fun <T> T.apply(block: T.() -> Unit): T
    public inline fun <T> T.also(block: (T) -> Unit): T
    

    这4个扩展函数内部都是直接调用block,而未进行任何特殊操作,区别仅在于接收的函数类型参数与返回值不同。可是要如何记忆这4个函数声明而不用每次都查阅其定义呢?或许写的多了自然能够熟练区分,但这里提供一种方法辅助记忆。

    首先思考一下,我们在什么时候使用这4个函数,两种常见的场景是:

    • 判空
    • 链式与嵌套调用

    现有一个可空类型的属性s和接收不可空类型的字符串处理方法op,当我们需要用op处理s时。使用progress1()会报错,Smart cast to 'String' is impossible, because 's' is a mutable property that could have been changed by this time;使用progress2()虽然消除了报错,但实际上依然不是安全的,且随意使用!!并不是一个好的编码习惯;这时我们需要用progress3()来正确判空。

    var s: String? = ""
    fun op(s: String) = ""
    fun progress1() {
        if (s != null) { op(s) }
    }
    fun progress2() {
        if (s != null) { op(s!!) }
    }
    fun progress3() {
        s?.let { op(it) }
        s?.run { op(this) }
        s?.also { op(it) }
        s?.apply { op(this) }
    }
    

    而这4个扩展函数,其实都可以正确判空,虽然在Kotlin编码习惯中,普遍更多的使用let函数。

    再来看链式与嵌套调用,通常这种使用方式主要是为了减少临时变量的引入。从链式与嵌套调用的整个流程来看,只有返回值的不同才能对这种链式操作造成影响,而这4个扩展函数只有2种返回类型。

    从链式与嵌套调用的单个操作的使用来看,我们可以使用函数引用和lambda表达式来向扩展函数传递参数,而这4个扩展函数只有两种函数参数。

    下面进行尝试,对于使用函数引用作为扩展函数参数的情况,我们前面说过,Kotlin会把相同功能是“方法”与“函数”看做同一种类型,因此4个扩展函数使用上并无差异。

    fun test(fragment: Fragment?) {
        fragment?.run(Fragment::isAlive)
        fragment?.let(Fragment::isAlive)
        fragment?.apply(Fragment::isAlive)
        fragment?.also(Fragment::isAlive)
    }
    

    对于使用lambda表达式作为扩展函数参数的情况,不同的lambda表达式类型的内部完全能实现相同的功能而仅有写法上的差异而已。

    fun test(fragment: Fragment?) {
        fragment?.run {
            activity != null
                    && activity?.isDestroyed == false
                    && activity?.isFinishing == false
                    && isAdded
                    && isDetached
        }
        fragment?.let {
            it.activity != null
                    && it.activity?.isDestroyed == false
                    && it.activity?.isFinishing == false
                    && it.isAdded
                    && it.isDetached
        }
    }
    

    以上我们证明了,对于所有情景,这4个扩展函数我们只需保留2个返回值类型不同的函数即可实现所有功能。2个3字母的扩展函数返回值为同一类型,2个非3字母的扩展函数返回值为同一类型。

    可以在3字母的函数中选择1个,在非3字母的函数中选择1个,作为开发中使用”主力“。例如,使用runapply函数,用于判空和链式与嵌套调用时使用,它们在lambda表达式内部用this表示接收者,可以省略this更方便的使用类方法。当然也可以使用letalso,lambda表达式内部用it表示参数,或者起一个更有意义的名字让程序更可读 。

    相关文章

      网友评论

          本文标题:Kotlin扩展

          本文链接:https://www.haomeiwen.com/subject/murlektx.html