擴充是指不直接修改類別定義的情況之下,增加類別的功能。
- 使用時機:當無法接觸某個類別定義,或者該類別未使用
open
修飾符,導致無法繼承時。
擴充函數 (Extension functions)
如何定義擴充函數?
需要在字首指定接收功能擴充的接收者類型 (receiver type)
範例:擴充 Int
增加一個判斷數字是否為偶數的函數
fun Int.isEven(): Boolean = this % 2 == 0
- 其中,接收者類型為
Int
,this
代表的是接收者。
如何使用?
擴充函數就跟原本呼叫函數的方式一樣。
2.isEven() //true
5.isEven() //false
若在超類別中定義擴充函數,則繼承該類別的子類別皆包含此擴充函數。
fun Any.printIt() = println(this)2.isEven().printIt() //true
3.isEven().printIt() //false"House".reversed().printIt()//esuoH
泛型擴充函數
如果希望能夠在 printIt
後方還能繼續處理輸入的值。我們可以將 printIt
改為用鏈式呼叫。
- 將
Any.printIt()
改成鏈式呼叫。
fun Any.printIt(): Any{
println(this)
return this
}"House".printIt().reversed().printIt()
→ 第一個 printIt()
沒有問題,但是第二個卻出錯了。
原因是第一個 printIt()
的回傳型別是 Any
,但是 reversed()
是String 的函數,所以沒有辦法直接使用。
- 將
printIt()
用泛型改寫:
fun <T> T.printIt(): T{
println(this)
return this
}
→ 以泛型參數作為接收者,這邊回傳的是 T
而不是 Any
,字串接收者的 String 類型,就會在鏈式呼叫過程中傳遞下去。
"House".printIt().reversed().printIt()
//House
//esuoH
擴充屬性 (Extension properties)
除了為類別增加功能擴充函數之外,還可以定義擴充屬性。
val String.numVowels
get() = count { "aeiouy".contains(it) }
- 一個有效的擴充屬性必須自行定義
get
或set
函數,以計算出應該回傳的屬性值。
"box".numVowels.printIt()
//1
不允許支援欄位 (backing field)
- 下列範例是不支援的
var String.preferredCharacters = 10
這時候 IDE 會提示我們改成用 getter
var String.preferredCharacters : Int
get() = 10
在前面的介紹中,我們知道在 Kotlin 的擴充(Extension) 可以用在類別函數以及類別屬性。並且可以同時將泛型運用在類別函數中,讓該類別函數可以更為通用。
那麼,為什麼需要擴充呢?
- 我們可以利用擴充函數,新增原本類別不存在的函數,在使用時,如同使用原本的類別一樣,直接呼叫即可,如果沒有辦法使用擴充,那麼會是怎麼情況呢?以 Java 為例,如果我們要在某一個類別中新增一個函數,因為 Java 不支援擴充,所以我們只能繼承該類別,並且新增新的函數。如此就會多寫了比較多的程式碼,另外,經過繼承後的類別,名稱也不會跟原本的相同。
- 我們也可以用擴充函數將同樣的函數(串)封裝在一個擴充函數中,只要程式看得到該擴充函數,就可以直接呼叫擴充函數,而不需要每次都呼叫相同的函數(串)。
- 如果擴充可空類別,那麼我們就可以在擴充函數中直接處理空值的情況,避免例外的發生。
利用擴充封裝程式碼
假設在程式中,經常需要將字串轉成大寫並且反轉。
toUpperCase().reversed()
我們可以利用擴充函數,將該函數串用擴充函數改寫。
fun String.reversedUpperCase() = this.toUpperCase().reversed()
如此,之後如果在程式中看到 toUpperCase().reversed()
都可以改用 reversedUpperCase()
來改寫,如此可以減少重複程式碼,也可以更精確地表達函數意義。
可空類別擴充
在可空類別定義擴充函數,就能夠在擴充函數本體內解決可能出現的空值問題。
fun String?.printWithDefault(default: String)
= print(this ?: default)fun print0(text:String?, default: String){
text.printWithDefault(default)
}fun main{
print0("print0", "default0")
print0(null, "default1")
}
//print0
//default1
infix 關鍵字
在擴充類別加上 infix
關鍵字,呼叫函式的時候,可以省略點運算子以及括弧。
例如:
infix fun String?.printWithDefault(default: String)
= print(this ?: default)
呼叫的時候就可以改為
null printWithDefault "default"
"Print" printWithDefault "default"
小結
擴充是 Kotlin 中相當強大的功能,它可以讓我們不需要繼承類別,就能夠替原有的類別新增功能。
使用擴充可以讓程式的可讀性增加,因為我們可以使用自定義的函數/屬性名稱,讓重複的程式碼藉由擴充來封裝。
將擴充使用在可空類別,就可以在可空類別中處理空值,如此就可以避免例外。
參考:
Kotlin 權威2.0:Android 專家養成術 — 第十八章:擴充
Kotlinlang.org: Extensions
Kotlin 讀書會:https://tw.kotlin.tips/study-jams/2