Extensions 是 Kotlin 一個強大的功能,它可以在不能改動 class 的情況下,擴充一個 function 在某個 class 上,也稱作 extension functions.
說明
Extension functions
在下面的範例當中,我們試著要對 MutableList 加上 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
}
如此一來,你在操作 MutableList 的時候,就可以使用 swap 這個方法了。
val list = mutableListOf(1, 2, 3)
println(list)
list.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'list'
println(list)
結果如下。
[1, 2, 3]
[3, 2, 1]
當然你也可以針對泛型進行延伸。
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
}
如果你在某個 class 使用 extension function,那麼它不會影響該類別的子類別,也就是說,即便你呼叫子類別的相同名稱 function,也只會呼叫子類別的 function。
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
結果如下。
Shape
如果原本的類別已經有該方法了,即便你在加入 extension function,也是會以該類別方法為優先。
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
結果如下。
Class method
不同的參數方法,視為不同的方法,所以 extension function 掛在哪,就是屬於哪個 function。
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType(i: Int) { println("Extension function") }
Example().printFunctionType(1)
Nullable receiver
你也可以對 null 加入 extension function。
fun main(args: Array<String>) {
println(null.toString())
}
fun Any?.toString(): String {
if (this == null) return "hello"
// 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()
}
結果如下。
hello
Extension properties
Kotlin 支援 extension properties,點進去 Array 的 lastIndex 屬性,可以看到原始碼長這樣。
public val <T> Array<out T>.lastIndex: Int
get() = size - 1
這邊有一個重要的觀念來自於 backing field,這邊表示 extension properties 因為有 backing field 所以不能直接初始化它,你必須透過 getter/setter 來進行初始化。
var name: String? = null
set(value) {
name = value
}
get() = name
看起來合情合理,但是實際上你如果執行看看會發現掉入無窮迴圈,這是為什麼呢?原因就出在 Kotlin 只要去存取 property 的時候,都會先呼叫 get(),這樣一來當我們在 set 這邊要取得 name 這個 property,就會去呼叫 get(),這樣一來就會變成下面這樣。
get() = get() = get() ...
無限跑下去的情況,程式就會掛掉,所以這時候我們就需要用到 backing field,將程式改成以下這樣。
var name: String? = null
set(value) {
field = value
}
get() = field
這時候就可以正常運作,這樣就是 backing field 存在的理由。
回到我們一開始所講的,因為我們不能直接初始化 extension properties,所以只要你透過以下方式初始化 extension property,那麼就會無法編譯。
val House.number = 1 // error: initializers are not allowed for extension properties
Companion object extensions
如果你有一個 companion object 也可以設定 extension function,定義跟使用方法跟一般 extension function 相同。
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion()
}
Scope of extensions
如果你將 extension function 寫在 top level,那麼只需要 import 相關 package 就可以直接使用。
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()
}
Declaring extensions as members
如果你在 class 內擴充別的類別的 instance function,在這個 extension function 就被稱作 dispatch receiver,那麼就必須宣告一個 member 去存取,否則是無法直接透過 extension function 直接存取。
fun main(args: Array<String>) {
Connection(Host("Hello"), 123).connect()
}
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() // calls Host.printHostname()
print(":")
printPort() // calls Connection.printPort()
}
fun connect() {
host.printConnectionString() // calls the extension function
}
}
結果如下。
Hello:123
如果你透過以下方法來編譯,就會出現錯誤。
// error, the extension function is unavailable outside Connection
Host("Hello").printConnectionString(123)
如果在同一個類別內,有出現相同名稱,可以透過 Qualified this (@classname) 來決定要用哪一個 instance 的方法。
fun main(args: Array<String>) {
Connection(Host("Hello"), 123).print()
}
class Host(val hostname: String) {
fun printInfo() { println(hostname) }
}
class Connection(val host: Host, val port: Int) {
fun printInfo() { println(port) }
fun print() {
host.getInfo() // calls the extension function
}
fun Host.getInfo() {
printInfo() // calls Host.toString()
this@Connection.printInfo() // calls Connection.toString()
}
}
結果如下。
Hello
123
下面有一個繼承的關係範例。
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() // call the extension function
}
}
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())
DerivedCaller().call(Base())
DerivedCaller().call(Derived())
}
結果如下,可以清楚看出相關的關係。
Base extension function in BaseCaller
//dispatch receiver is resolved virtually
Base extension function in DerivedCaller
//extension receiver is resolved statically
Base extension function in DerivedCaller
參考資料
https://kotlinlang.org/docs/reference/extensions.html#extension-properties