feintkotlin
以下内容皆有feint进行翻译,不定期进行更新。翻译若有不正确的地方,欢迎在评论中指出。
扩展
Kotlin和 C#、Gosu一样,提供一种不需要类继承和使用任何像装饰者模式这样的设计模式,在一个类上扩展新的功能的能力。这之所以行得通,是因为一种特别的声明,叫做扩展。Kotlin支持函数扩展和属性扩展。
扩展函数
声明一个扩展函数的时候, 我们需要在它的名字前加上 接收者类型 的前缀, 也就是被扩展的那个类型。 接下来添加了一个 swap 函数到 MutableList<Int>:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' 相当于 list
this[index1] = this[index2]
this[index2] = tmp
}
在扩展函数中的关键字 this,代表着接收者对象(在点前面点那个)。现在我们可以在任何 MutableList<Int>中调用这个函数:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' 在 'swap()' 中会保持 'l' 的值
当然了,这个函数对于任何 MutableList<T> 都是有意义点, 我们还可以使用范型:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' 相当于 list
this[index1] = this[index2]
this[index2] = tmp
}
我们在函数名称之前声明范型类型参数,这样就可以在接受者类型的表达式中使用。参照 范型.
扩展是静态解析的
使用扩展实际上并没有修改它所扩展的类。 定义一个扩展时, 你没有往类中插入新的成员, 只不过是这个类型的变量使用点号进行了新的函数调用。
我们要强调宽展函数是静态分发的, 也就是说它们没有被接受者类型实例化。 这意味着扩展函数的调用,是由发起函数调用的表达式的类型所决定, 而不是运行时刻表达式所获得的结果的类型。 例如:
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".
可以为空的接收者
注意:扩展可以通过可以为空的接收者类型定义。这个扩展可以在一个对象变量上调用, 甚至如果它的值是空的,可以在函数体内部检查 this == null 。 下面这个例子,允许可以不用检查类型是否为空,直接调用 toString() :检查发生在扩展函数的内部。
fun Any?.toString(): String {
if (this == null) 返回 "null"
// 经过空值检查, 'this' 被自动转换成非空类型, 所以接下来允许使用 toString()
// 交由Any类的成员函数解决
return toString()
}
扩展属性
和函数一样,Kotlin也支持属性扩展:
val <T> List<T>.lastIndex: Int
get() = size - 1
注意:扩展没有实际的往类中插入了成员, 也就没有办法让扩展属性拥有 辅助字段。这也就是为什么扩展属性没法使用初始化器。 它们只能显示的 getters/setters 来定义。
举个例子:
val Foo.bar = 1 // 错误:扩展属性不允许使用初始化器
伙伴对象扩展
如果一个类中定义了一个伙伴对象, 你也可以为伙伴对象定义扩展属性和扩展函数:
class MyClass {
companion object { }// 将会被称作 "Companion"
}
fun MyClass.Companion.foo() {
// ...
}
就像伙伴对象中的标准成员一样, 它们的调用仅仅通过类名进行限制:
MyClass.foo()
扩展的作用域
大部分时间我们在顶级域中定义扩展, 也就是直接在包下面:
package foo.bar
fun Baz.goo() { ... }
在定义扩展的包外面使用它, 我们需要在使用它的地方进行导入:
package com.example.usage
import foo.bar.goo // 通过名字 "goo" 导入所有扩展
// 或是
import foo.bar.* // 从 "foo.bar" 导入所有的东西
fun usage(baz: Baz) {
baz.goo()
}
详细信息请参照 导入 。
将扩展声明为成员
在一个类内部,你可以为其他的类声明扩展。 在这个扩展内部, 有多个 隐式接收者 - 对象成员可以直接访问。 扩展声明中的类实例被称作 派生接收者, 并且扩展方法的接收者类型的实例叫做 扩展接收者.
class D {
fun bar() { ... }
}
class C {
fun baz() { ... }
/**
* D是扩展接收者;C是派生接收者
*/
fun D.foo() {
bar() // 调用 D.bar
baz() // 调用 C.baz
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
}
在派生接收者和扩展接收者中成员的名字存在歧义的情况下,扩展接收者优先。像要引用派生接收者中的成员,你可以使用 this 限定的语法.
class C {
fun D.foo() {
toString() // 调用 D.toString()
this@C.toString()// 调用 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() // 调用扩展函数
}
}
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
使用动机
在Java中,我们习惯命名为 "*Utils": FileUtils, StringUtils 等等。 著名的 java.util.Collections 也是一样的。 并且这些 Utils-classes 类令人不愉快的点在于,使用它们的代码是这样子的:
// 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类中所有可能的方法, 对不? 这也就是扩展帮助到我们的地方。
本文暂时没有评论,来添加一个吧(●'◡'●)