如何使用 Extensions

如何使用 Extensions

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