[譯]Kotlin泛型中何時該用型別形參約束?

極客熊貓發表於2018-10-23

翻譯說明:

原標題: When (and when not) to Use Type Parameter Constraints in Kotlin

原文地址: typealias.com/guides/when…

原文作者: Dave Leeds

之前的Kotlin文章,歡迎檢視:

翻譯系列:

原創系列:

實戰系列:

簡述(又來皮一下):

今天這篇文章依舊很簡單,只要搞懂一個東西就可以了。那就是泛型中的型別形參的約束,這個概念在Java中也有的。但是我們有個疑惑是什麼情況下使用泛型型別形參呢?

進入正題(開始表演...)

當你在宣告一個泛型時,Kotlin允許你給這個泛型的型別形參增加約束條件,換言之就是把型別形參可接受的型別實參限制在一個型別範圍內。那這個有什麼作用呢?讓我們一起來看下面這個例子:

想像下有這麼個需求場景: 假設你家裡有幾隻寵物,你想選擇一個最喜歡的:

fun <T> chooseFavorite(pets: List<T>): T {
    val favorite = pets[random.nextInt(pets.size)]
    // This next line won't compile - because `name` can't be resolved
    println("My favorite pet is ${favorite.name}")
    return favorite
}
複製程式碼

println()函式宣告不會通過編譯,因為你無法通過T來引用name屬性。因為T可以是呼叫者指定的任何內容。例如,像以下程式碼實現,T就是一個Int型別,很明顯它就沒有name屬性:

chooseFavorite(listOf(1, 2, 3))
複製程式碼

解決方法一 - 放棄使用泛型

你可以嘗試改造一下這個函式,把泛型去掉:

fun chooseFavorite(pets: List<Pet>): Pet {
    val favorite = pets[random.nextInt(pets.size)]
    println("My favorite pet is ${favorite.name}")
    return favorite
}
複製程式碼

這個處理看起來貌似沒有什麼問題,但是我們會遇到一種不想要的返回值型別。以下是我們呼叫該函式時會發生的情況:

val pets: List<Dog> = listOf(Dog("Rover"), Dog("Sheba"))
val favorite: Pet = chooseFavorite(pets)
複製程式碼

儘管我們宣告定義的是List< Dog >,但是通過chooseFavorite返回的是一個Pet型別,除非這裡我們使用強制型別轉換。

解決辦法二 - 使用型別形參約束

我們可以通過指定上限來限制型別形參-換句話說就是指定你想要接收的超型別。在我們的例子中,我們希望此函式作為List <Pet>處理List <Dog>List <Cat>也就是既包含Cat型別也包含Dog型別的混合Pet型別的列表中。

fun <T : Pet> chooseFavorite(pets: List<T>): T {
    val favorite = pets[random.nextInt(pets.size)]
    println("My favorite pet is ${favorite.name}")
    return favorite
}
複製程式碼

這裡的型別形參的宣告是<T:Pet>,這個Pet就是上界約束。現在我們已經指定了這個,呼叫程式碼只能傳遞Pet型別以及它的子型別。

val pets: List<Dog> = listOf(Dog("Rover"), Dog("Sheba"))
val favorite: Dog = chooseFavorite(pets)
複製程式碼

使用建議

下面兩種是你需要使用型別形參約束情況:

  1. 當你在某個型別上呼叫特定的函式或屬性(即某個型別的類獨有的函式和屬性)
  2. 當你希望在函式返回時保留某個特定型別

這是一個快速的“備忘單”表,可幫助您決定哪種情況使用什麼?

需要呼叫成員(類的成員函式或屬性) 不需要呼叫成員(類的成員函式或屬性)
需要保留型別 使用帶有型別引數約束的泛型 使用不帶型別引數約束的泛型
不需要保留型別 使用非泛型和適當的抽象 使用Java中的原生態型別

如何指定約束

約束還有很多 - 您可以指定多個引數的約束,以及對同一引數的多個約束。對於所有細節,請檢視概念文章Type Parameter Constraint。您還可以在Kotlin的官方參考文件中快速檢視常用約束。

讀者有話說

本篇文章核心點在於什麼情況下該使用泛型約束,作者總結的很好就是那種表格,理解和掌握了那張表格,那麼你在使用泛型形參約束上就會胸有成竹。關於那個表格可能有點難理解,我這裡再補充解釋一下:

  • 首先,解釋下是否需要呼叫成員,意思是在定義函式宣告內部是否使用了泛型形參約束上界型別中對應類的特定成員包括函式或屬性,比如說chooseFavorite方法中的name屬性就是Pet這個類特定的成員屬性。總之一句話: 在函式定義內部是否需要呼叫特定型別的類的成員或屬性,這也就直接決定了是否需要帶型別引數約束,如果不需要呼叫成員,那麼就不需要帶型別引數約束
  • 然後,解釋下是否需要保留型別,就是在定義一個函式的時候,返回值的型別是否要保留泛型形參約束上界型別,主要作用就是避免強制型別轉換,例如例子中解決辦法一就是去除泛型,而是用了抽象的父類Pet,即使傳入的是List<Dog>,但是chooseFavorite方法返回依然是父類Pet,外部接收型別Dog所以避免不了強制型別轉換。總之一句話: 是否需要保留型別,也就直接決定了是否使用泛型,如果不保留型別的話可以不使用泛型,函式外部接收者可以使用抽象的父類來接收;如果保留型別,函式外部接收者就必須是明確一個型別,那麼此時如果還用抽象父類就避免不了型別轉換,那麼此時就應該使用泛型
  • 最後,得出簡單的總結:

1、是否需要呼叫成員決定了在使用泛型前提下,是否使用泛型型別引數約束;否則直接可以使用抽象

2、是否保留型別決定了是否使用泛型

3、既保留型別又呼叫成員,則就是使用泛型且帶形參約束條件

4、不保留型別又呼叫成員,則就是不使用泛型和適當的抽象

5、保留型別不呼叫成員,則就是使用泛型不帶形參約束條件

6、不保留型別不呼叫成員,則就是使用java中原生態型別,例如Java中的List型別

[譯]Kotlin泛型中何時該用型別形參約束?

歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~

相關文章