原文地址: Kotlin學習快速入門(7)——擴充套件的妙用 - Stars-One的雜貨小窩
之前也模模糊糊地在用這個功能,也是十分方便,可以不用繼承,快速給某個類增加新的方法,本篇便是來講解下Kotlin中擴充套件這一概念的使用
說明
先解釋一下,擴充套件的說明,官方文件上解釋:
Kotlin 能夠擴充套件一個類的新功能,而無需繼承該類或者使用像裝飾者這樣的設計模式
簡單來說,就是可以不用繼承來讓一個類多出一個方法或屬性(成員變數),可能這樣說比較抽象,我們以一個簡單的例子來說
比如說,我們需要用到以下功能:
判斷String物件是否其是否為null或未空白字串,如果為null或空白字串,則返回true,否則返回false
此功能挺好實現,但我們想要實現此功能,無非就是3種方法:
- 寫個工具類StringUtil,然後傳遞有個String物件進去,方法返回
- 寫個新的類,讓其繼承於String類,之後再新增方法
- 用裝飾者模式,擴充套件類(這裡不多解釋裝飾者模式,可以自己百度查閱下資料)
但上面的方法,估計第一種各位都明白,也是十分簡單,但使用起來還是比較麻煩,還得將物件作為入參傳遞,如果使用Kotlin的擴充套件特性,還能變得更加簡單
而剩下兩種,改動均是較大,一般得看情況使用,也是不太推薦
擴充套件方法
我們以剛才上述說的功能為例,實現判斷String物件是否其是否為null或未空白字串,如果為null或空白字串,則返回true,否則返回false
此功能
語法及使用
首先,顯示講解下語法
fun 類名.方法名(引數列表...):返回值{
}
看起來稍微有些抽象,我們直接上示例:
fun String.isBlankOrNullString(): Boolean {
return this == null || this.trim().length == 0
}
需要注意的是,方法裡的this就是當前呼叫此方法的String物件
擴充套件方法使用:
fun main(args: Array<String>) {
val str = ""
println(str.isBlankOrNullString())
}
PS: 這裡的擴充套件方法寫在了頂層,是全域性可用的
注意點
- 擴充套件方法會區分作用域(全域性和區域性)
- 類中存在於擴充套件方法同名同引數列表,相當於過載,此時以擴充套件方法為主
- 擴充套件方法可接收可空型別
擴充套件方法作用域
擴充套件方法的宣告位置,會決定此擴充套件方法的作用域
如下面示例:
fun main() {
val str = ""
println(str.isBlankOrNullString())
}
class User {
val str = ""
}
fun String.isBlankOrNullString(): Boolean {
return this == null || this.trim().length == 0
}
我們將方法寫在了最外層(即與class關鍵字同級),此時,我們可以在任意的類中呼叫此方法
但如果我們稍微改一下,如下:
fun main() {
val str = ""
//這裡會報錯!!
//println(str.isBlankOrNullString())
}
class User {
val str = ""
fun sayHello() {
//類中可以正常使用
str.isBlankOrNullString()
}
fun String.isBlankOrNullString(): Boolean {
return this == null || this.trim().length == 0
}
}
擴充套件方法過載問題
由於是宣告方法,可能會出現方法名重名的情況,即我們Java基礎中說到的過載關係
這裡,如果出現了過載的情況(方法名和引數列表相同),會以類中的方法為主(即會忽略掉擴充套件方法)
上面此句,是根據文件上總結得來的,實際上也是測試通過
fun main() {
Example().printFunctionType()
}
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
最後輸出的是Class method
但這裡有個奇怪的情況,我以String的擴充套件為例,測試發現與上述結論不一致!!
以下是我的測試程式碼:
fun main() {
val str = ""
println(str.isNullOrBlank())
}
fun String.isNullOrBlank(): Boolean {
println("進入我們的方法裡")
return this == null || this.trim().length == 0
}
最終輸出:
進入我們的方法裡
true
看著列印,這明顯就是進到我們定義的擴充套件方法裡啊 ?
研究了一番,發現原本的那個isNullOrBlank()
,並不是String類中含有的方法
官方也是通過擴充套件方法來實現追加的,且是擴充套件的類是CharSequence
,而且此類是個介面類,所有實現了此介面的類都有了isNullOrBlank()
方法
而我們自己也是定義了個擴充套件方法,與官方的擴充套件方法發生了過載,於是我們的擴充套件方法便是把官方的擴充套件方法覆蓋了
所以得出以下結論:
當類中存在某個方法,擴充套件方法與此方法發生過載關係,會以類中方法為主
某類已存在某個擴充套件方法,使用者自定義擴充套件方法與該擴充套件方法發生過載,會以使用者自定義擴充套件方法為主
當然,上面這是自己研究定下的,若是不太準確,希望各位可以指正! ?
擴充套件方法接收可空型別
上面,我們是定義的String型別擴充套件,當然,也可以給String?
可空型別進行擴充套件方法
這樣寫也是沒有問題的:
fun String?.isNullOrBlank(): Boolean {
println("進入我們的方法裡")
return this == null || this.trim().length == 0
}
像這樣,我們還可以直接給所有型別(Any
型別)增加個toString()
方法的擴充套件方法:
fun Any?.toString(): String {
if (this == null) return "null"
// 空檢測之後,“this”會自動轉換為非空型別,所以下面的 toString()
// 解析為 Any 類的成員函式
return toString()
}
但是,這個擴充套件方法是不起作用的!!
為什麼呢?因為Any物件已經存在了toString()
此方法,根據上面的結論,會以類中的方法優先!
擴充套件屬性
除了方法,我們也可以實現擴充套件屬性
語法
val 型別.屬性名: 屬性型別名
get() =
如有個示例,判斷檔案是否為md檔案:
val File.isMdFile: Boolean
get() = extension.toLowerCase()=="md"
使用:
fun main() {
val file =File("D:\\tt.md")
println(file.isMdFile)
}
val File.isMdFile: Boolean
get() = extension.toLowerCase()=="md"
相關作用域與上述擴充套件方法講解的是一致的,這裡不再贅述
擴充套件伴生物件
這裡,感覺就是類似工具類的擴充套件吧,如果使用伴生物件,之後就可以類名.方法名
去呼叫方法(類似Java中的靜態方法)
如果我們想追加一些方法,也可以使用擴充套件來實現,如下例子
class MyClass {
companion object { } // 將被稱為 "Companion"
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion()
}
原理補充
Kotlin中的擴充套件函式,其實最後編譯成class檔案都會轉為一個靜態方法
這一過程實際上是由Kotlin編譯器替我們實現了,我們只管吃語法糖就完事了!
我們以下面方法為例:
fun String?.isNullOrBlank(): Boolean {
println("進入我們的方法裡")
return this == null || this.trim().length == 0
}
最終生成的靜態方法:
// 這個類名就是頂層檔名+“Kt”字尾
public final class ExtendsionDemoKt {
// 擴充套件函式 isNullOrBlank 對應實際上是 Java 中的靜態函式,並且傳入一個接收者型別物件作為引數
public static final boolean isNullOrBlank(@NotNull CharSequence $this$isNullOrBlank) {
Intrinsics.checkParameterIsNotNull($this$isNullOrBlank, "$this$isNullOrBlank");
String var1 = "進入我們的方法";
boolean var2 = false;
System.out.println(var1);
return StringsKt.trim($this$isNullOrBlank).length() == 0;
}
}
如果我們isNullOrBlank還有引數的話,靜態方法中除了CharSequence這個引數,還會多出其他引數
PS: 可以點開對應的class檔案,然後使用tool->kotlin->Decompile Kotlin To Java,將class還原會java程式碼