Kotlin 學習筆記 (15) — 擴充

Andy Lu
Nov 8, 2020

--

擴充是指不直接修改類別定義的情況之下,增加類別的功能。

  • 使用時機:當無法接觸某個類別定義,或者該類別未使用 open 修飾符,導致無法繼承時。

擴充函數 (Extension functions)

如何定義擴充函數?

需要在字首指定接收功能擴充的接收者類型 (receiver type)

範例:擴充 Int 增加一個判斷數字是否為偶數的函數

fun Int.isEven(): Boolean = this % 2 == 0
  • 其中,接收者類型為 Intthis 代表的是接收者。

如何使用?

擴充函數就跟原本呼叫函數的方式一樣。

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 改為用鏈式呼叫。

  1. Any.printIt() 改成鏈式呼叫。
fun Any.printIt(): Any{
println(this)
return this
}
"House".printIt().reversed().printIt()

→ 第一個 printIt() 沒有問題,但是第二個卻出錯了。

原因是第一個 printIt() 的回傳型別是 Any ,但是 reversed() 是String 的函數,所以沒有辦法直接使用。

  1. 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) }
  • 一個有效的擴充屬性必須自行定義 getset 函數,以計算出應該回傳的屬性值。
"box".numVowels.printIt()
//1

不允許支援欄位 (backing field)

  • 下列範例是不支援的
var String.preferredCharacters = 10

這時候 IDE 會提示我們改成用 getter

var String.preferredCharacters : Int
get() = 10

在前面的介紹中,我們知道在 Kotlin 的擴充(Extension) 可以用在類別函數以及類別屬性。並且可以同時將泛型運用在類別函數中,讓該類別函數可以更為通用。

那麼,為什麼需要擴充呢?

  1. 我們可以利用擴充函數,新增原本類別不存在的函數,在使用時,如同使用原本的類別一樣,直接呼叫即可,如果沒有辦法使用擴充,那麼會是怎麼情況呢?以 Java 為例,如果我們要在某一個類別中新增一個函數,因為 Java 不支援擴充,所以我們只能繼承該類別,並且新增新的函數。如此就會多寫了比較多的程式碼,另外,經過繼承後的類別,名稱也不會跟原本的相同。
  2. 我們也可以用擴充函數將同樣的函數(串)封裝在一個擴充函數中,只要程式看得到該擴充函數,就可以直接呼叫擴充函數,而不需要每次都呼叫相同的函數(串)。
  3. 如果擴充可空類別,那麼我們就可以在擴充函數中直接處理空值的情況,避免例外的發生。

利用擴充封裝程式碼

假設在程式中,經常需要將字串轉成大寫並且反轉。

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

--

--

Andy Lu
Andy Lu

Written by Andy Lu

Android/Flutter developer, Kotlin Expert, like to learn and share.

No responses yet