文章目錄
- 定義
- 泛型屬性
- 泛型函數
- 泛型類或接口
- where 聲明多個約束
- 泛型具體化
- in、out 限制泛型輸入輸出
定義
有時候我們會有這樣的需求:一個類可以操作某一類型的對象,并且限定只有該類型的參數才能執行相關的操作。
如果我們直接指定該類型Int
,確實可以實現該操作,但是換一個類型就得再重新定義一個類:
class MyArray {fun add(int: Int) {}fun delete(int: Int) {}
}fun main() {val myArray = MyArray()myArray.add(1)myArray.delete(1)
}
如果我們將類型改為Any
,又會導致可操作參數太過籠統,還容易出現各種問題:
class MyArray {fun add(any: Any) {}fun delete(any: Any) {}
}fun main() {val myArray = MyArray()myArray.add(1)myArray.delete("abc")
}
這時候我們需要引入泛型。泛型的定義方式如下:
<泛型名[: 泛型所屬類型], ...>
一般情況下,泛型名會是單個的大寫字母,很多時候是T
,而泛型所屬類型可以指定泛型是某個類/接口或者其子類/實現(當然,單獨這樣寫做不了什么,請繼續往下看):
<T>
<T: Runnable>
<K, V>
泛型屬性
在 Kotlin 中,可以為一個類定義擴展屬性:
class MyClassval MyClass.name get() = "MyClass"
如果我們要指定是任何類都有的屬性,則可以泛型。
泛型屬性只能用在擴展,聲明時需要將泛型定義放置在val|var
后,變量名前。
val <T> T.name get() = "Any"fun main() {val int = 1print(int.name)
}
Any
也可以限制該泛型是Runnable
接口或其實現。
val <T: Runnable> T.name get() = "Runnable"fun main() {val int = 1// print(int.name) 不可訪問print(Runnable { }.name)
}
Runnable
泛型函數
我們也可以為函數泛型,與泛型擴展屬性類似,泛型定義需要寫在fun
和函數名之間(意外發現此時as
如果無法轉換不會拋出異常,而是返回any
):
fun <T> cast(any: Any) = any as Tfun main() {cast<List<Double>>(listOf(1, 2)).forEach { print(it); print(", ") }
}
1.0, 2.0,
Note:泛型可以作為函數接收者、參數、返回值類型的一部分。如果一個泛型函數在調用時可以通過傳入的參數類型或返回值聲明確定泛型的類型,則調用時泛型的類型可以缺省,否則需要指明其類型
函數名<泛型類型, ...>()
(泛型類與此類似)。
聲明兩個泛型(涉及到的 Kotlin 一些便捷的習語我們將在后續文章學習):
fun <K, V> hashMap(vararg pairs: Pair<K, V>) =HashMap<K, V>().apply { putAll(pairs) }fun main() {print(hashMap("key" to 1))
}
{key=1}
泛型擴展函數:
// List 類是一個泛型類
// 可以將它的泛型指定為函數的泛型
fun <T> List<T>.print() = print(this)fun main() {listOf(1, 2, 3).print()
}
[1, 2, 3]
fun <T: Comparable<Int>> T.isGreaterZero() = this > 0fun <T: Comparable<Int>> T.isGreaterZero() = this > 0fun main() {val int = 1// Int 實現了 Comparable<Int>// 因此可以調用print(int.isGreaterZero())
}
true
作為返回值:
fun <T> Any.toType() = this as Tfun main() {// 沒有指定變量類型,無法推斷泛型的類型// 因此調用時需要聲明泛型類型val doubleList = listOf<String>().toType<List<Double>>()val intList: List<Double> = listOf<String>().toType()
}
泛型類或接口
在定義泛型類或接口時,需要將泛型定義放置在類名之后,括號之前。
泛型函數與接口是類似的,這里以類為例。
class Group<T>(vararg elements: T) {private val value = mutableListOf(*elements)fun add(element: T) = value.add(element)fun remove(element: T) = value.remove(element)// 重寫 toString,調用 print 時即可打印出列表override fun toString(): String = value.toString()
}fun main() {val group = Group(1, 2, 3) // Group<Int>(1, 2, 3)println(group)group.remove(2)println(group)group.remove(4)print(group)
}
[1, 2, 3]
[1, 3]
[1, 3]
Note:
vararg
用于定義數量不定的參數,vararg element: Int
表示你可以傳入任意數量(0…無限大)的Int
類型,在內部訪問element
會獲取到一個數組Array<Int>
,而使用*
可以將其展開為vararg
(也就是原始傳入的任意數量的Int
),可以用于傳入另一個需要vararg
的函數。
使用*
可以指代所有泛型(下方例子中相當于Any?
,*
無法用在函數調用上):
fun List<*>.print() = print(this)fun main() {listOf(1, 2, 3).print()println()listOf("a", "b", "c").print()
}
[1, 2, 3]
[a, b, c]
where 聲明多個約束
我們可以使用<T: 類型>
來約束泛型為某一類型或其子類。但有時候我們想要將其約束于多個類型,可以使用where
來實現(此時<T: 類型>
中的類型也應該一并移到where
處):
我們先定義一個接口和一個類,后面的例子會反復用到:
interface MyInterfaceclass MyClass : Runnable, MyInterface {override fun run() {}
}
-
泛型擴展屬性
// 只有實現了 Runnable 和 MyInterface 才能擁有此擴展屬性 val <T> T.id where T: Runnable, T: MyInterfaceget() = 123fun main() {MyClass().id }
-
泛型函數
// 只有實現了 Runnable 和 MyInterface 才能擁有此擴展函數 fun <T> T.getId() where T : Runnable, T : MyInterface = 123// 只有泛型 T 實現了 Runnable 和 MyInterface 才能使用此函數 fun <T> getName() where T : Runnable, T : MyInterface ="Runnable&MyInterface"fun main() {MyClass().getId()getName<MyClass>() }
-
泛型類或接口
// 只有實現了 Runnable 和 MyInterface 才能作為構造參數 value 傳入 class MyContainer<T>(val value: T) where T : Runnable, T : MyInterfacefun main() {MyContainer(MyClass()) }
泛型具體化
有時候我們需要判斷泛型的類型,會想到用類型::class
獲取對應的KClass
進行對比。而當該類型為泛型時,我們需要將其具象化reified
,通常該聲明只能用于函數,并且需要與inline
搭配使用:
inline fun <reified T> T.isInt() = T::class == Int::classfun main() {println(1.isInt())print(false.isInt())
}
true
false
in、out 限制泛型輸入輸出
in
和out
修飾泛型只能用在類或接口。
當一個泛型被in
修飾時,表明該類型只能作為對象屬性、方法的輸入(賦值、傳參):
class Group<in T>(vararg elements: T) {// 因為泛型聲明為 in,屬性類型中包含 T 可見性只能為 privateprivate val value = mutableListOf(*elements)fun add(element: T) {}// 因為泛型聲明為 in,不能定義返回 T 類型的方法// fun get(index: Int) = value[index]
}
當一個泛型被in
修飾時,表明該類型只能作為對象屬性、方法的輸出(獲取值、函數返回):
class Group<out T>(vararg elements: T) {// 因為泛型聲明為 out,而 MutableList<T> 具有 in 和 out// 若將其公開,將會允許 in T,例如 value.add(element: T)// 屬性類型中包含 T 可見性只能為 privateprivate val value = mutableListOf(*elements)// 因為泛型聲明為 out,不能作為參數類型// fun add(element: T) {}fun get(index: Int) = value.get(index)
}
Note:注意是針對對象而言,如果是一個類聲明了
out
泛型,仍然可以將該泛型作為構造函數中的參數類型。class Group<out T>(vararg elements: T)