Kotlin开发知识Kotlin编程

Kotlin面向对象之扩展(Extensions)

2017-05-24  本文已影响19人  已迁至知乎_此不再维护

Kotlin与C#和Gosu类似,可以为一个类扩展一个新功能,但是不必继承该类或使用设计模式,如装饰器模式。Kotlin是通过被称为扩展的特殊声明完成这项工作的。Kotlin支持函数扩展和属性扩展。

函数扩展(Extension Functions)

为了声明一个扩展函数,我们需要以接收者类型作为该函数的前缀,也就是说:接收者类型就是被扩展的类型。如下示例了为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关键字代表着被扩展类型的对象(.操作符前的对象)。现在,我们可以使用MutableList<Int>类型的任意对象调用已经扩展的函数:

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
}

我们在扩展函数名称之前声明泛型参数,使其在目标接收类型表达式中可用。

扩展被静态解析(Extensions are resolved statically)

扩展的实现并没有真实的修改被扩展的类。扩展的定义,并没有在目标类中添加一个新的成员,仅是可以使目标类型的对象通过.操作符调用扩展函数而已。

要强调的是扩展函数是被静态调用的,也就是说它们并非目标类型的真实方法。意思就是说被调用的扩展函数被函数体调用的表达式类型决定(形参类型),而非运行时看到的实际传入类型。例如:

open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())

该示例的结果将打印出c,因为被调用的扩展函数仅取决于函数声明时候的参数c的类型,是类C的实例。

如果一个类有成员函数,且具有相同函数签名的扩展函数,则在调用的时候将调用成员函数,如:

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

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

如果我们以C类的任意实例c来调用c.foo()方法,结果将是打印member,而非extension。

但是,扩展函数可以重载具有相同名称但不同签名的成员函数,这是完全可行的:

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

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

调用C().foo(1)方法将打印extension

被扩展的类可以为null(Nullable Receiver)

请注意,可以使用可null的目标类型定义扩展。即使其值为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()
}

属性扩展(Extension Properties)

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

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

由于扩展的实现并没有在目标类中插入真实的成员,因此不能够让一个扩展属性拥有一个备份字段(backing field)。这就是为什么初始化块不允许初始化扩展属性。它们只能通过显式提供getter/setter方法来操作。例如:

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

伴生对象扩展(Companion Object Extensions)

如果一个类定义了伴生对象,则可以为该伴生对象扩展函数或属性:

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

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

与伴生对象的常规成员类似,只能使用类名作为限定词来调用:

MyClass.foo()

范围扩展(Scope of Extensions)

大多数情况下,我们定义扩展都是直接在包下的最高层级进行:

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()
)

声明扩展作为成员(Declaring Extensions as Members)

在一个类中,您可以为另一类声明扩展。在这样的扩展中,将存在多个隐式接收器 - 可以在没有限定符的情况下就访问到的对象成员。 声明扩展的类的实例称为调用接收者,扩展方法的接收类型称为扩展接收者。

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
    }
}

在调用接收者的成员与扩展接收者成员之间发生命名冲突的情况下,扩展接收者优先。要引用调用接收者的成员,可以使用如下语法:

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

扩展的设计目的(Motivation)

在Java中,我们习惯使用名为“* Utils”的类:FileUtilsStringUtils等。著名的java.util.Collections属于同一范畴。对于这些Utils类的使用,通常会令人不快的:

// 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类中实现所可能的方法,对吧?这就是扩展帮助我们的地方。

上一篇下一篇

猜你喜欢

热点阅读