Kotlin扩展

2020-09-07  本文已影响0人  chym

扩展函数

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表示参数,或者起一个更有意义的名字让程序更可读 。

上一篇下一篇

猜你喜欢

热点阅读