专业的编程技术博客社区

网站首页 > 博客文章 正文

Kotlin官方文档翻译,类和对象:扩展

baijin 2024-09-06 14:53:25 博客文章 5 ℃ 0 评论

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类中所有可能的方法, 对不? 这也就是扩展帮助到我们的地方。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表