Kotlin类和对象 (五)--- 扩展

1、什么是扩展

类似于C#Gosu,Kotlin 提供了向一个类扩展新功能的能力, 而不必继承这类, 也不必使用像装饰者模式的任何设计模式。 这种功能是通过一种特殊的声明来实现的, Kotlin 中称为 扩展(extension).
Kotlin 支持 扩展函数(extension function)扩展属性(extension property)

2、扩展函数

要声明一个扩展函数, 我们需要在函数名之前添加前缀, 表示这个函数的 接收者类型(receiver type), 表明我们希望扩展的类型。如下,为MutableList<Int> 增加一个 swap 功能:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
        val tmp = this[index1] // 'this' corresponds to the list
        this[index1] = this[index2]
        this[index2] = tmp
    }

扩展函数中 的 this 关键字 指 的是接收者类型(receiver type, 即MutableList<Int>.之前所对应的类型) 。现在我们可以对 任意 MutableList<Int> 调用 swap函数:

val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'

当然,这个功能对 MutableList<T>也是使用的,我们可以扩展成通用类型的:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

2.1、扩展是静态解析的

扩展实际上不会修改它们所扩展的类。通过定义一个扩展,不会向类中添加新函数,而是使用这种类型的变量通过.来调用新功能。

强调一点,扩展函数是静态分发的,即它们不是虚拟的,是依赖接收者类型的。这意味着 扩展函数的调用是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例:

open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())  // result is "c”

打印的结果是“c”.因为最终调用的是 c 的扩展函数。

如果类中存在成员函数, 同时又在同一个类上定义了同名的扩展函数, 并且与调用时指定的参数匹配, 这种情况下 总是会优先使用成员函数。

class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }

如果调用 c.foo(),打印的结果是“member”.

不过,如果扩展函数复写了同名的成员函数,但是具有不同的参数,这完全是可以的。根据参数调用不同的函数即可:

class C {
    fun foo() { println("member") }
}

fun C.foo(i: Int) { println("extension") }

如果调用 c.foo(),打印的结果是“extension”.

2.2、可空接收类型

扩展可以被定义成一个可空的接收类型。这样的扩展可以用 对象变量来调用,即使它的值为null,并且可以在扩展函数内通过“this == null”来检查接收者的空状态。这就是为什么在 Kotlin中调用 toString() 而不用检查 null(空检查发生在扩展函数内部)。

fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check, 'this' is autocast to a non-null type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

3、扩展属性

与扩展函数类似, Kotlin 也支持扩展属性。

val <T> List<T>.lastIndex: Int
    get() = size - 1

注意 : 由于扩展属性实际上不会向类添加新的成员, 因此无法让一个扩展属性拥有一个 幕后属性。 所以, 对于扩展属性不允许存在初始化. 扩展属性的行为只能通过明确给定的取值方法与设值方法来定义。

// error: initializers are not allowed for extension properties
val Foo.bar = 1 

4、伴随对象(companion object)扩展

对于伴随对象,也支持扩展函数和扩展属性。

class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.foo() {
    // ...
}

和 伴随对象中的普通成员一样,扩展函数和扩展属性的也只能由 类名调用:

MyClass.foo()

5、扩展区间

扩展函数通常定义成顶层函数:

package foo.bar

fun Baz.goo() { ... } 

那么在该包的外面使用该扩展,需要在使用时导入该扩展:

package com.example.usage

import foo.bar.goo // importing all extensions by name "goo"
                   // or
import foo.bar.*   // importing everything from "foo.bar"

fun usage(baz: Baz) {
    baz.goo()
}

6、将扩展声明为成员(定义在类内部,以限制其作用域。)

在一个类中,可以声明另一个类的扩展。在这样的扩展内,有多个隐式接收者- 这些接收者的对象成员可以不用限定符就可以被访问。

声明扩展的类的实例被称为 分发接收者dispatch receiver),而扩展函数的接收者类型所对应的类的实例被称为 扩展接收者extension receiver):

class D {
    fun bar() { ... }
}

class C {
    fun baz() { ... }

    fun D.foo() {
        bar()   // calls D.bar
        baz()   // calls C.baz
    }

    fun caller(d: D) {
        d.foo()   // call the extension function
    }
}

在 分发接收者 和 扩展接收者 之间存在名称冲突的情况下,扩展接收方优先。
若需要调用发送接收方的成员 ,可以通过“this@ClassName”方式:

class C {
    fun D.foo() {
        toString()         // calls D.toString()
        this@C.toString()  // calls C.toString()
    }

这种方式的扩展函数,可以定义成 open, 被子类覆盖。
也就是说,这些函数的分发 对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。

open class D {
}

class D1 : D() {
}

open class C {
    open fun D.foo() {
        println("D.foo in C")
    }

    open fun D1.foo() {
        println("D1.foo in C")
    }

    fun caller(d: D) {
        d.foo()   // call the extension function
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }

    override fun D1.foo() {
        println("D1.foo in C1")
    }
}

C().caller(D())   // prints "D.foo in C"
C1().caller(D())  // prints "D.foo in C1" - dispatch receiver is resolved virtually
C().caller(D1())  // prints "D.foo in C" - extension receiver is resolved statically

7、动机(Motivation)

在java 中,我们通常将类命名为Utils": FileUtils, StringUtils等等。很有名的“java.util.Collections”也是这样命名的。令人不愉快部分是,使用这些工具类的代码像这样:

// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list));

那些类名总是碍事。我们可以静态导入大道这样的效果:

// Java
swap(list, binarySearch(list, max(otherList)), max(list));

这会变得好一点,但是我们并没有从 IDE 强大的自动补全功能中得到帮助。如果能这样就更好了:

// Java
list.swap(list.binarySearch(otherList.max()), list.max())

但是我们不想实现 List 中所有可能的方法,对吧? 扩展刚好可以帮助到我们。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值