Kotlin Extensions扩展(翻译)

卢毅
2023-12-01

Extensions

Kotlin语言拥有不通过继承基类和使用类似与装饰器模式的设计模式而去为一个类去增加一个新的功能的能力。
这种能力称为Extensions。Kotlin支持extension functions 和 extension properties。

Extension Functions

去申明一个extension function。我们需要将被被添加申明的类别前置作为接受类型(receiver type),之后添加需要扩展的方法。
例如给 MutableList<Int>添加一个swap方法,如下:

//为MultableList<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
}
//Receiver Object 调用扩展方法
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'

在扩展方法里面的this关键词代表的是通过点号. 传递过来的当前调用对象(receiver object)
当然这个方法对任何MultableList<T>都有效,所以我们可以使这个方法更加的通用

// 针对MultablleList<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

Extension并没有真正的去修改所扩展的类。当申明一个扩展,并不需要添加新的内容成员到一个类中,仅仅是为这个类型的变量创建了一个可
通过. 调用的可调用方法。
值得强调的是Extension Function是静态派遣的。换句话说,他们不是由receiver type 虚拟出来的,这个扩展函数是由调用Extension Function的表达式的类型所决定,
然不是在运行时执行表达式的结果类型所决定。
For example:

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

上述举例 的表达式结果是打印C ,因为Extension Functions被调用是通过参数c的类型Class c 。

注意: 当receiver type 的扩展函数 Extension Functions 的函数名和type的成员函数名字相冲突,成员函数永远首先被调用,
除非扩展函数的参数形式等与成员函数有所区分。

Nullable Receiver

扩展函数Extension Functions 可以为可空对象类型定义。这样的Extension Functions能够被可空对象变量所调用。通常可以在函数体内部进行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

注意,因为Extension 并不是真正的在类中添加实际成员,所以并没有有效的方法为扩展属性提供支持区域(backing field)。这就是为什么初始化中是不允许扩展属性的,他们只能通过明确提供getter/setter方法的形式所定义。
Example:

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() {
// ...
}

就像伴随对象的成员一样,你可以只通过类名作为修饰符来访问Extension
如上的
MyClass.foo()

Scope of Extensions

大多数情况,我们在文件最上面,申明包名的下面 ,申明定义Extension

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.foo()
}

Declaring Extensions as Members

在一个类的内部,你可以为别的类申明定义扩展。在这样的扩展中,有许多个不用特定限定符就可以访问其对象成员的隐式接受者(Receiver)
定义申明扩展Extension的那个类的实例叫做调度接受器(Dispatch Receiver),为其定义扩展方法的那个接收器类别的实例叫做扩展接收器(Extension Receiver)

class D {
fun bar() { ... }
}
class C {
fun baz() { ... }
fun D.foo() {
//调用Extension Receiver 的方法
bar()
// calls D.bar
//调用Dispatch Receiver 的方法
baz()
// calls C.baz
}
fun caller(d: D) {
d.foo()
// call the extension function
}
}

防止因为dispatch receiver和extension receiver 的成员的名字在Extension Functions中冲突,可以使用this+限定符的语法来避免、

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

Extensions declared as members can be declared as open and overridden in subclasses. This means that the dispatch of such
functions is virtual with regard to the dispatch receiver type, but static with regard to the extension receiver type.
扩展作为成员被申明可以被申明为open,并且能在子类中被复写。这就意味着在派遣这样的扩展对dispatch receiver来说可以是虚拟的,但是对extension receiver 类型则是静态的。
可以理解为 dispatch receiver 为 extension receiver 派遣什么样的扩展


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())
C1().caller(D())
C().caller(D1())
// prints "D.foo in C"
// prints "D.foo in C1" - dispatch receiver is resolved virtually
// prints "D.foo in C" - extension receiver is resolved statically

可见性注意事项

Extensions 在统一作用范围内使用和其他常规函数相同的可见性规则。
* 扩展函数申明在文件顶部拥有访问同一文件中其他private申明的对象的权限。
* 如果扩展函数申明在其receiver type外部,则其没有访问receiver type object的私有成员的权限。

Motivation

来一点点原汁原味的动机
In Java, we are used to classes named “*Utils”: FileUtils , StringUtils and so on. The famous java.util.Collections
belongs to the same breed. And the unpleasant part about these Utils-classes is that the code that uses them looks like this:
// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)),
Collections.max(list));
Those class names are always getting in the way. We can use static imports and get this:
// Java
swap(list, binarySearch(list, max(otherList)), max(list));
This is a little better, but we have no or little help from the powerful code completion of the IDE. It would be so much better if we
could say:
// Java
list.swap(list.binarySearch(otherList.max()), list.max());
But we don’t want to implement all the possible methods inside the class List , right? This is where extensions help us.

 类似资料: