Kotlin進階知識(九)——泛型類型參數

引言:和Java不一樣,Kotlin始終要求類型實參要麼被顯式地說明,要麼能被編譯器推導出來。例如,在Java中,能夠聲明List類型的變量,而不須要說明它能夠包含哪類事物。而Kotlin從一開始就有泛型,因此它**不支持沒有類型參數的泛型類**(即原生態類型),類型實參必須定義markdown

1、泛型函數和屬性

泛型函數:編寫一個使用列表的函數,要求在任何列表(通用的列表)上使用,而不是某個具體類型的元素的列表,這個函數即爲泛型函數。app

泛型函數有它本身的類型形參。這些形參每次函數調用時都必須替換成具體類型實參ide

大部分使用集合的庫函數都是泛型的。來看看圖1中的slice函數。這個函數返回一個只包含在指定下標區間內的元素。函數

圖1:slice泛型函數的類型形參爲T

接受者和返回類型用到了函數的類型形參T,它們的類型都是List<T>。在一個具體的列表上調用這個函數時,能夠顯式地指定類型實參。但大部分狀況下無需聲明,由於編譯器會推導出類型。性能

  • 調用泛型函數
fun main(args: Array<String>) {
    genericFunctionTest()
}

fun genericFunctionTest() {
    val letters = ('a' .. 'z').toList()

    // 顯式地指定類型實參
    println(letters.slice<Char>(0 .. 2))

    // 編譯器推導出這裏的T是Char
    println(letters.slice(10 .. 14))
}

// 輸出結果
[a, b, c]
[k, l, m, n]
複製代碼
  • 聲明泛型的擴展屬性
// 這個泛型擴展屬性能在任何種類元素的列表上調用
val <T> List<T>.penultimate: T
    get() = this[size - 2]

// 在此次調用匯總,類型參數T被推導成Int
>>> println(listOf(1, 2, 3, 4).penultimate)
3
複製代碼

不能聲明泛型非擴展屬性 普通(即非擴展)屬性不能擁有類型參數,不能在一個類的屬性中存儲多個不一樣類型的值。ui

2、聲明泛型類

和Java同樣,Kotlin經過在類名稱後加上一對尖括號,並把類型參數放在尖括號內來聲明泛型類泛型接口。一旦聲明以後,就能夠在類的主體內像其餘類型同樣使用類型參數。this

// List接口定義了類型參數T
interface List<T> {
    // 在接口或類的內部,T能夠看成普通類型使用
    operator fun get(index: Int): T
    // ...
}
複製代碼

若是自定義的類繼承了泛型類(或者實現了泛型接口),就得爲基礎類型的泛型形參提供一個類型實參。它能夠是具體類型或者另外一個類型形參:spa

// 這個類實現了List,提供了具體類型實參:String
class StringList: List<String> {
    // 注意T如何被String代替
    override fun get(index: Int): String = ... }

// 如今ArrayList的泛型類型形參T就是List的類型實參
class ArrayList: List<T> {
    override fun get(index: Int): T = ... }
複製代碼

StringList類被聲明成只能包含String元素,因此它使用String做爲基本類型的類型實參。code

ArrayList類定義了它本身的類型參數T並把它指定爲父類的類型實參。orm

3、類型參數約束

類型參數約束能夠限制做爲(泛型)類和(泛型)函數的類型實參的類型。

以計算列表元素之和的函數爲例。它能夠用在List和List上,但不能夠用在List這樣的列表上。能夠定義一個類型參數約束,說明sum類型形參必須是數字,來表達這個限制。

上界約束:在泛型類型具體的初始化中,其對應的類型實參必須是這個具體類型或者它的子類型

定義:把冒號放在類型參數名稱 以後,做爲類型形參上界的類型緊隨**其後**,以下:

// Java 
<T extends Number> T sum(List<T> list)

// Kotlin
fun <T: Number> List<T>.sum(): T
複製代碼

一旦指定了類型形參T的上界,就能夠把類型T的值看成它的上界(類型)的值使用。例如:能夠調用定義在上界類的方法:

// 指定Number爲類型形參的上界
fun <T: Number> oneHalf(value: T): Double {
    // 調用Number類中的方法
    return value.toDouble() / 2
}

fun oneHalfTest() {
    println(oneHalf(100))
}
複製代碼
  • 爲一個類型參數指定多個約束
fun <T> ensureTrailingPeriod(seq: T)
        // 類型參數約束的列表
        where T: CharSequence, T: Appendable {
    // 調用爲CharSequence接口定義的擴展函數
    if(!seq.endsWith('.')) {
        // 調用Appendable接口的方法
        seq.append('.')
    }
}

fun ensureTrailingPeriodTest() {
    val helloWorld = StringBuilder("Hello World")
    ensureTrailingPeriod(helloWorld)
    println(helloWorld)
}

// 輸出結果
Hello World.
複製代碼

這種狀況下,能夠說明做爲類型實參的類型必須實現**CharSequenceApppendable兩個接口**。這意味着該類型的值能夠使用訪問數據endsWith)和修改數據append)兩個操做。

4、讓類型形參非空

若聲明的是泛型類或者泛型函數,任何類型實參,包括那些可空的類型實參,均可以替換他的形參類型。

事實上,沒有指定上界類型形參將會使用**Any?這個默認的上界**。

class Processor<T> {
    fun process(value: T) {
        value?.hashCode()
    }
}
複製代碼

process 函數中,參數value是可空的,儘管T沒有使用問號標記

若想保證替換類型形參始終是非空類型,能夠經過指定一個約束來實現。若除了可空性以外沒有任何限制,能夠使用**Any代替默認的Any?做爲上界**:

// 指定非「空」上界
class ProcessorNew<T: Any> {
    fun process(value: T) {
        // 類型T的值如今是非「空」的
        value.hashCode()
    }
}
複製代碼

約束<T: Any>確保了類型T永遠都是非空類型

相關文章
相關標籤/搜索