Kotlin Vocabulary | 使用 Kotlin 中的擴展提高代碼可讀性

您有過想給某個類的 API 添加新的功能或屬性嗎?html

一般您能夠經過繼承該類,或者建立一個新的函數,該函數接收該類的實例做爲參數,從而解決這個問題。Java 編程語言一般使用 Utils 類來解決此類問題,但這樣的方式並不支持代碼自動補全,會讓寫出的代碼比較難以查找,使用起來也不直觀。雖然這兩種方式均可以解決問題,但終究仍是很難寫出簡潔易讀的代碼。編程

值得慶幸的是,Kotlin 帶着 擴展函數和屬性 來 "拯救" 咱們了。經過它,您無需使用繼承,或建立接收類實例的函數便可爲某個類添加功能。同 Java 這類編程語言不一樣,Android Studio 的自動補全功能是支持 Kotlin 擴展的。擴展能夠用於第三方代碼庫、Android SDK 以及用戶自定義的類。編程語言

繼續閱讀,探索如何經過擴展來提高您的代碼可讀性。函數

擴展函數的使用

咱們假設您有一個叫作 Dog 的類,它有 name、breed、age 三個屬性。工具

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

data class Dog(val name: String, val breed: String, val age: Int)

領養機構但願擴展 Dog 類,使其具備打印狗狗信息的功能,這樣能夠方便感興趣的人來領養。爲此咱們實現一個擴展函數,方法同實現一個普通的函數是同樣的,除了一點: 您須要在函數名前面加上要擴展的類名以及一個 "." 符號。在函數體中,您可使用 this 來引用接收者對象,在該函數做用域內可以訪問到接收者所屬類的所有成員對象。this

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun Dog.printDogInformation() {
  println("Meet ${this.name}, a ${this.age} year old ${this.breed}")
}

調用 printDogInformation() 方法就同調用其它 Dog 類中的函數同樣。編碼

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun main() {
  val dog = Dog("Jen", "Pomeranian", 13)
  dog.printDogInformation()
}

從 Java 代碼中調用擴展函數code

擴展函數並不屬於咱們要擴展的類的一部分,所以當咱們在 Java 語言中嘗試調用該方法時,並不能在該類的其它方法中找到它。正如咱們稍後所看到的,擴展會在其被定義的文件中反編譯成靜態方法,並接收一個咱們要擴展的類的實例做爲參數。如下就是在 Java 中調用 printDogInformation() 擴展函數的示例代碼。orm

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

DogExtensionKt.printDogInformation(dog);

爲 nullable 類型定義擴展函數htm

您也能夠爲 nullable 類型定義擴展函數。與其在調用擴展函數以前進行 null 檢查,咱們能夠直接爲 nullable 類型定義擴展函數,讓擴展函數自己包含對 null 的檢查。如下就是爲 nullable 類型定義擴展函數 printInformation() 的示例代碼。

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun Dog?.printInformation() {
  if (this == null){
    println("No dog found")
    return
  }
  println("Meet ${this.name} a ${this.age} year old ${this.breed}")
}

您能夠發現,調用 printInformation() 函數時並不須要作 null 檢查。

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun main() {
  val dog : Dog? = null
  dog.printInformation() // prints "No dog found"
}

擴展屬性的使用

做爲領養機構,可能還想知道狗狗的年齡是否符合被領養的條件,所以咱們實現了一個名爲 isReadyToAdopt 的擴展屬性,用於檢查狗狗的年齡是否超過 1 歲。

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

val Dog.isReadyToAdopt: Boolean
get() = this.age > 1

調用此屬性就同調用 Dog 類中的其它屬性同樣。

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun main() {
  val dog1 = Dog("Jen", "Pomeranian", 13)
  if(dog1.isReadyToAdopt){
    print("${dog1.name} is ready to be adopted")
  }
}

擴展函數中的複寫

您並不能在擴展函數裏複寫類中現有的成員函數。若是您所定義的擴展函數同已有的成員函數簽名一致,那麼只有現有的成員函數會被正常調用,由於函數調用取決於變量聲明時的靜態類型,而不是存儲在該變量中值的運行時類型。例如,您不能在 String 上擴展 toUppercase() 方法,可是您能夠擴展一個名爲 convertToUppercase() 的方法。

當您擴展了一個不屬於您定義的類型,而該類型所在的代碼庫中存在一個同您的擴展具備相同簽名的擴展函數,那麼上述所說的這種行爲就會顯現出後果。在這種狀況下,會調用代碼庫中的擴展函數,而您所獲得的惟一信息是您所定義的擴展函數變成了一個未被使用的方法。

工做原理

咱們能夠在 Android Studio 中對 printDogInformation() 反編譯,方法是在 Tools/Kotlin/Show Kotlin Bytecode 中點擊 Decompile 按鈕。如下是反編譯 printDogInformation() 以後生成的代碼:

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

public static final void printDogInformation(@NotNull Dog $this$printDogInformation) {
  Intrinsics.checkParameterIsNotNull($this$printDogInformation, "$this$printDogInformation");
  String var1 = "Meet " + $this$printDogInformation.getName() + ", a " + $this$printDogInformation.getAge() + " year old " + $this$printDogInformation.getBreed();
  boolean var2 = false;
  System.out.println(var1);
}

實際上,擴展函數看起來只是普通的、接收一個類實例做爲參數的靜態函數,與接收類並無任何其它聯繫。這就是爲何代碼沒有 Backing Fields 的緣由——它們實際上並無在類中插入任何成員。

總結

總的來講,擴展是一個頗有用的工具。在使用擴展時需仔細思慮,請牢記如下提示,讓您的代碼更直觀和易讀。

提示:

  • 擴展是靜態分發的;
  • 成員函數永遠是 "贏家";
  • 領養一隻狗狗!

祝您編碼愉快!

相關文章
相關標籤/搜索