引言:和Java不一樣,
Kotlin
始終要求類型實參要麼被顯式地說明,要麼能被編譯器推導出來。例如,在Java中,能夠聲明List類型的變量,而不須要說明它能夠包含哪類事物。而Kotlin
從一開始就有泛型,因此它**不支持
沒有類型參數的泛型類**(即原生態類型),類型實參必須定義
。markdown
泛型函數:編寫一個使用列表的函數,要求在任何列表(通用的列表)上使用,而不是某個具體類型的元素的列表,這個函數即爲泛型函數。app
泛型函數有它本身的類型形參。這些形參在每次函數調用時都必須替換成具體的類型實參。ide
大部分使用集合的庫函數都是泛型的。來看看圖1中的slice函數。這個函數返回一個只包含在指定下標區間內的元素。函數
接受者和返回類型用到了函數的類型形參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
和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
類型參數約束能夠限制做爲(泛型)類和(泛型)函數的類型實參的類型。
以計算列表元素之和的函數爲例。它能夠用在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.
複製代碼
這種狀況下,能夠說明做爲類型實參的類型必須實現**CharSequence
和Apppendable
兩個接口**。這意味着該類型的值能夠使用訪問數據(endsWith
)和修改數據(append
)兩個操做。
若聲明的是泛型類或者泛型函數,任何類型實參,包括那些可空的類型實參,均可以替換他的形參類型。
事實上,沒有指定上界的類型形參將會使用**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
永遠都是非空類型!