kotlin的扩展功能
Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展 的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为 扩展函数 。此外,也有 扩展属性 , 允许你为一个已经存在的类添加新的属性。
扩展函数
声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。下面代码为 Int 添加一个swap 函数:
fun Int.swap1(index1: Int, index2: Int): Int {
return index1 + index2 - this//this就是调用这个方法的对象
}
//这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象)
var i: Int = 5
i.swap1(2,5)
当然,扩展也可以对扩展使用
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
// 这个函数对任何 MutableList<T> 起作用
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
为了在接收者类型表达式中使用泛型,我们要在函数名前声明泛型参数
扩展是静态解析的
扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。
我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
//输出接口Shape
fun printClassName(s: Rectangle) {
println(s.getName())
}
printClassName(Shape())
//输出接口Rectangle
就是根据调用的是时候这个对象是什么类型,就调用什么类型的扩展方法,
如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、 相同的名字,并且都适用给定的参数,这种情况总是取成员函数。 例如
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
//Class method
扩展函数重载同样名字但不同参数成员函数也可以使用
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType(i: Int) { println("Extension function") }
Example().printFunctionType(1)
// Extension function
可空接收者
可以为空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为 null,并且可以在函数体内检测 this == null,这能让你在没有检测 null 的时候调用 Kotlin 中的toString():检测发生在扩展函数的内部。
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
// 解析为 Any 类的成员函数
return toString()
}
这里就产生了另外一个问题,上面说到
如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、 相同的名字,并且都适用给定的参数,这种情况总是取成员函数。
那么我们定义的这个toString()方法应该和所有的类的toString()方法相同,就永远不可能被调用,为什么还有这样的用法呢?因为我们是为Example?
注册的扩展函数,按照道理是当运行时是null类型,null
类型没有任何方法,所以先调扩展方法,我们在扩展方法里通过空判断,确定了类型,那么在扩展方法里调用的方法就是具体类型的方法
class Example {
fun toString1(): String {
println("toString")
return "xx"
}
}
fun Example?.toString1(): String {
println("toString1")
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
// 解析为 Any 类的成员函数
println("toString2")
return toString1()
}
var s : Example = Example()
println(s.toString1())
//输出内容
toString
xx
var s : Example? = Example()
println(s.toString1())
//输出内容
toString1
toString2
toString
xx
扩展属性
与函数类似,Kotlin 支持扩展属性:
val <T> List<T>.lastIndex: Int
get() = size - 1
val House.number = 1 // 错误:扩展属性不能有初始化器
由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。
伴生对象的扩展
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数与属性。就像伴生对象的常规成员一样, 可以只使用类名作为限定符来调用伴生对象的扩展成员:
class MyClass {
companion object { } // 将被称为 "Companion"
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion()
}
扩展的作用域
大多数时候我们在顶层定义扩展——直接在包里:
package org.example.declarations
fun List<String>.getLongestString() { /*……*/}
要使用所定义包之外的一个扩展,我们需要在调用方导入它:
package org.example.usage
import org.example.declarations.getLongestString
fun main() {
val list = listOf("red", "green", "blue")
list.getLongestString()
}
扩展声明为成员
在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 隐式接收者 —— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为 分发接收者,扩展方法调用所在的接收者类型的实例称为 扩展接收者
class Host(val hostname: String) {
fun printHostname() { print(hostname) }
}
class Connection(val host: Host, val port: Int) {
fun printPort() { print(port) }
fun Host.printConnectionString() {
printHostname() // 调用 Host.printHostname()
print(":")
printPort() // 调用 Connection.printPort()
}
fun connect() {
/*……*/
host.printConnectionString() // 调用扩展函数
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
//Host("kotl.in").printConnectionString(443) // 错误,该扩展函数在 Connection 外不可用
}
//=========================输出========================
// kotl.in:443
对于分发接收者与扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用 限定的 this 语法。
class Connection {
fun Host.getConnectionString() {
toString() // 调用 Host.toString()
this@Connection.toString() // 调用 Connection.toString()
}
}
声明为成员的扩展可以声明为 open 并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
open class Base { }
class Derived : Base() { }
open class BaseCaller {
open fun Base.printFunctionInfo() {
println("Base extension function in BaseCaller")
}
open fun Derived.printFunctionInfo() {
println("Derived extension function in BaseCaller")
}
fun call(b: Base) {
b.printFunctionInfo() // 调用扩展函数
}
}
class DerivedCaller: BaseCaller() {
override fun Base.printFunctionInfo() {
println("Base extension function in DerivedCaller")
}
override fun Derived.printFunctionInfo() {
println("Derived extension function in DerivedCaller")
}
}
fun main() {
BaseCaller().call(Base()) // “Base extension function in BaseCaller”
DerivedCaller().call(Base()) // “Base extension function in DerivedCaller”——分发接收者虚拟解析
DerivedCaller().call(Derived()) // “Base extension function in DerivedCaller”——扩展接收者静态解析
}
// ====================== 输出 ===========================
Base extension function in BaseCaller
Base extension function in DerivedCaller
Base extension function in DerivedCaller