文章目录
定义
有时候我们会有这样的需求:一个类可以操作某一类型的对象,并且限定只有该类型的参数才能执行相关的操作。
如果我们直接指定该类型Int
,确实可以实现该操作,但是换一个类型就得再重新定义一个类:
kt
class MyArray {
fun add(int: Int) {}
fun delete(int: Int) {}
}
fun main() {
val myArray = MyArray()
myArray.add(1)
myArray.delete(1)
}
如果我们将类型改为Any
,又会导致可操作参数太过笼统,还容易出现各种问题:
kt
class MyArray {
fun add(any: Any) {}
fun delete(any: Any) {}
}
fun main() {
val myArray = MyArray()
myArray.add(1)
myArray.delete("abc")
}
这时候我们需要引入泛型。泛型的定义方式如下:
<泛型名[: 泛型所属类型], ...>
一般情况下,泛型名 会是单个的大写字母,很多时候是T
,而泛型所属类型 可以指定泛型是某个类/接口 或者其子类/实现(当然,单独这样写做不了什么,请继续往下看):
kt
<T>
<T: Runnable>
<K, V>
泛型属性
在 Kotlin 中,可以为一个类定义扩展属性:
kt
class MyClass
val MyClass.name get() = "MyClass"
如果我们要指定是任何类都有的属性,则可以泛型。
泛型属性只能用在扩展,声明时需要将泛型定义放置在val|var
后,变量名前。
kt
val <T> T.name get() = "Any"
fun main() {
val int = 1
print(int.name)
}
Any
也可以限制该泛型是Runnable
接口或其实现。
kt
val <T: Runnable> T.name get() = "Runnable"
fun main() {
val int = 1
// print(int.name) 不可访问
print(Runnable { }.name)
}
Runnable
泛型函数
我们也可以为函数泛型,与泛型扩展属性类似,泛型定义需要写在fun
和函数名之间(意外发现此时as
如果无法转换不会抛出异常,而是返回any
):
kt
fun <T> cast(any: Any) = any as T
fun main() {
cast<List<Double>>(listOf(1, 2)).forEach { print(it); print(", ") }
}
1.0, 2.0,
Note:泛型可以作为函数接收者、参数、返回值类型的一部分。如果一个泛型函数在调用时可以通过传入的参数类型或返回值声明确定泛型的类型,则调用时泛型的类型可以缺省,否则需要指明其类型
函数名<泛型类型, ...>()
(泛型类与此类似)。
声明两个泛型(涉及到的 Kotlin 一些便捷的习语我们将在后续文章学习):
kt
fun <K, V> hashMap(vararg pairs: Pair<K, V>) =
HashMap<K, V>().apply { putAll(pairs) }
fun main() {
print(hashMap("key" to 1))
}
{key=1}
泛型扩展函数:
kt
// List 类是一个泛型类
// 可以将它的泛型指定为函数的泛型
fun <T> List<T>.print() = print(this)
fun main() {
listOf(1, 2, 3).print()
}
[1, 2, 3]
kt
fun <T: Comparable<Int>> T.isGreaterZero() = this > 0
fun <T: Comparable<Int>> T.isGreaterZero() = this > 0
fun main() {
val int = 1
// Int 实现了 Comparable<Int>
// 因此可以调用
print(int.isGreaterZero())
}
true
作为返回值:
kt
fun <T> Any.toType() = this as T
fun main() {
// 没有指定变量类型,无法推断泛型的类型
// 因此调用时需要声明泛型类型
val doubleList = listOf<String>().toType<List<Double>>()
val intList: List<Double> = listOf<String>().toType()
}
泛型类或接口
在定义泛型类或接口时,需要将泛型定义放置在类名之后,括号之前。
泛型函数与接口是类似的,这里以类为例。
kt
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?
,*
无法用在函数调用上):
kt
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
处):
我们先定义一个接口和一个类,后面的例子会反复用到:
kt
interface MyInterface
class MyClass : Runnable, MyInterface {
override fun run() {}
}
-
泛型扩展属性
kt// 只有实现了 Runnable 和 MyInterface 才能拥有此扩展属性 val <T> T.id where T: Runnable, T: MyInterface get() = 123 fun main() { MyClass().id }
-
泛型函数
kt// 只有实现了 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>() }
-
泛型类或接口
kt// 只有实现了 Runnable 和 MyInterface 才能作为构造参数 value 传入 class MyContainer<T>(val value: T) where T : Runnable, T : MyInterface fun main() { MyContainer(MyClass()) }
泛型具体化
有时候我们需要判断泛型的类型,会想到用类型::class
获取对应的KClass
进行对比。而当该类型为泛型时,我们需要将其具象化reified
,通常该声明只能用于函数,并且需要与inline
搭配使用:
kt
inline fun <reified T> T.isInt() = T::class == Int::class
fun main() {
println(1.isInt())
print(false.isInt())
}
true
false
in、out 限制泛型输入输出
in
和out
修饰泛型只能用在类或接口。
当一个泛型被in
修饰时,表明该类型只能作为对象属性、方法的输入(赋值、传参):
kt
class Group<in T>(vararg elements: T) {
// 因为泛型声明为 in,属性类型中包含 T 可见性只能为 private
private val value = mutableListOf(*elements)
fun add(element: T) {}
// 因为泛型声明为 in,不能定义返回 T 类型的方法
// fun get(index: Int) = value[index]
}
当一个泛型被in
修饰时,表明该类型只能作为对象属性、方法的输出(获取值、函数返回):
kt
class Group<out T>(vararg elements: T) {
// 因为泛型声明为 out,而 MutableList<T> 具有 in 和 out
// 若将其公开,将会允许 in T,例如 value.add(element: T)
// 属性类型中包含 T 可见性只能为 private
private val value = mutableListOf(*elements)
// 因为泛型声明为 out,不能作为参数类型
// fun add(element: T) {}
fun get(index: Int) = value.get(index)
}
Note:注意是针对对象而言,如果是一个类声明了
out
泛型,仍然可以将该泛型作为构造函数中的参数类型。
ktclass Group<out T>(vararg elements: T)