文章目录
Kotlin 泛型
概述
-
泛型是对程序的一种抽象,可以借助泛型对代码的复用。
-
支持类型检查,能在编译期检查出问题。
-
还可以在定义泛型时,设置边界限制。
-
从型变的位置来分类的话,分为使用处型变和声明处型变。
-
从型变的父子关系来分类的话,分为逆变和协变。逆变表示父子关系颠倒了,而协变表示父子关系和原来一致。
-
型变的口诀:泛型作为参数,用 in;泛型作为返回值,用 out。在特殊场景下,同时作为参数和返回值的泛型参数,我们可以用 @UnsafeVariance 来解决型变冲突。
简单使用
通过泛型定义 Controller
控制类,泛型 T 代表传入类型。
open class Animal {
override fun toString(): String {
return "Animal"
}
}
class Dog : Animal() {
override fun toString(): String {
return "Dog"
}
}
class Cat : Animal() {
override fun toString(): String {
return "Cat"
}
}
class Controller<T> {
fun showAnimal(t: T) {
println(t.toString())
}
}
fun main() {
val controller = Controller<Animal>()
controller.showAnimal(Animal())
controller.showAnimal(Dog())
controller.showAnimal(Cat())
}
说明:方法 showAnimal 可以接受不同类型的对象,并依次打印出内容。
泛型函数
在 Kotlin 中函数是一等公民。
fun <T> showAnimal(t: T) {
println(t.toString())
}
fun main() {
showAnimal(Animal())
showAnimal(Dog())
showAnimal(Cat())
}
说明:效果与泛型类一样。
泛型边界
- 在 Java 中可以通过
<? extends Base>
指定上界是 Base 类型,表示前者是后者的子类。 - 在 Kotlin 中可以通过
T : Base
指定上界是Base
类型。
class Apple {
override fun toString(): String {
return "Apple"
}
}
fun <T:Animal> showAnimal(t: T) {
println(t.toString())
}
fun main() {
showAnimal(Animal())
showAnimal(Dog())
showAnimal(Cat())
// showAnimal(Apple()) // 提示错误
}
限制多个类型
可以通过 where
关键字实现。
// 吃鱼接口
interface IFish {}
// 吃骨头接口
interface IBone {}
class Dog : Animal(), IBone {
override fun toString(): String {
return "Dog"
}
}
class Cat : Animal(), IFish {
override fun toString(): String {
return "Cat"
}
}
fun <T> showEatFish(t: T) where T : Animal, T : IFish {
println(t.toString())
}
fun main() {
showEatFish(Cat())
// showEatFish(Animal()) // 提示错误
// showEatFish(Dog()) // 提示错误
}
型变
- 型变是为了解决泛型的不变性问题。
- 根据位置分类:
- 使用处型变
- 声明处型变
- 根据父子关系分类:
- 逆变:负责关系颠倒
- 协变:父子关系不变
不变性问题
虽然 Cat 是 Animal 的子类,但是 MutableList<Cat>
与 MutableList<Animal>
不存在任何关系(包括继承关系),这就是泛型的不变性。
fun showAnimalList(list: MutableList<Animal>) {
list.forEach {
println(it.toString())
}
}
fun main() {
val animalList = mutableListOf<Animal>(Animal())
showAnimalList(animalList)
val catList = mutableListOf<Cat>(Cat())
// showAnimalList(catList) // 报错
}
说明:
- showAnimalList() 函数只能接收
MutableList<Animal>
类型的集合,如果传入MutableList<Animal>
类型就会报错,提示类型不匹配。 - 虽然 Animal 与 Cat 存在父子关系,但是
MutableList<Animal>
与MutableList<Cat>
没有任何关系,因此传入不同类型的对象会报错,这就是泛型的不变性。
协变 out
协变表示输出、生产、获取,使用 out 关键字。
Kotlin 中的 <out T>
类似于 Java 中的 <? extends T>
。
协变问题
fun getAnimal(list: MutableList<Animal>): Animal {
return if (list.size > 0) {
list[0]
} else {
Animal()
}
}
fun main() {
val animalList = mutableListOf<Animal>(Animal())
getAnimal(animalList)
val catList = mutableListOf<Cat>(Cat())
// getAnimal(catList) // 报错,提示类型不匹配
}
说明:getAnimal() 函数要求传入 MutableList<Animal>
类型,而实际传入 MutableList<Cat>
类型,因此报错提示类型不匹配。
解决方式一:使用处协变
在 getAnimal() 函数的参数的泛型类型前面添加 out 关键字。
这样代码就可以通过编译了,可以看作将 MutableList<Cat>
看作为 MutableList<Animal>
的子类。这称为”泛型的协变“。
// 使用处协变
// ↓
fun getAnimal(list: MutableList<out Animal>): Animal {
return if (list.size > 0) {
list[0]
} else {
Animal()
}
}
解决方式二:声明处协变
在 FruitShop 类的泛型参数前面添加 out 关键字
// 声明处协变
// ↓
class AnimalProducer<T : MutableList<out Animal>>(private val animalList: T) {
fun getAnimal(): Animal {
return if (animalList.size > 0) {
animalList[0]
} else {
Animal()
}
}
}
// 使用:
val catList = mutableListOf<Cat>(Cat())
val animalProducer = AnimalProducer(catList)
animalProducer.getAnimal()
逆变 in
逆变表示输入、消费、传递,使用 in 关键字。
Kotlin 中的 <in T>
类似于 Java 中的 <? super T>
。
逆变问题
fun deleteAnimal(list: MutableList<Cat>) {
if (list.size > 0)
list.removeAt(0)
}
fun main() {
val catList = mutableListOf<Cat>(Cat())
deleteAnimal(catList)
val deleteAnimal = mutableListOf<Animal>(Animal())
// setAnimal(animalList) // 报错,提示类型不匹配
}
说明:提示类型不匹配。
解决方式一:使用处逆变
在 deleteAnimal() 函数的参数中的泛型类型前面添加 in 关键字。
这样代码就可以通过编译了,可以将 MutableList<Animal>
看作为 MutableList<Cat>
的子类。这称为”泛型的逆变“。
// 使用处逆变
// ↓
fun deleteAnimal(list: MutableList<in Cat>) {
if (list.size > 0)
list.removeAt(0)
}
解决方式二:声明处逆变
在 AnimalConsumer 类的泛型参数前面添加 in关键字
// 声明处逆变
// ↓
class AnimalConsumer<T : MutableList<in Cat>>(private val catList: T) {
fun consume() {
if (catList.size > 0)
catList.removeAt(0)
}
}
// 使用:
val animalList = mutableListOf<Animal>(Animal())
val consumer = AnimalConsumer(animalList)
consumer.consume()
数组拷贝
fun <T> copyIn(src: Array<T>, dest: Array<in T>) {
if (dest.size < src.size) {
throw IndexOutOfBoundsException()
} else {
src.forEachIndexed { index, value ->
dest[index] = src[index]
}
}
}
fun <T> copyOut(src: Array<out T>, dest: Array<T>) {
if (dest.size < src.size) {
throw IndexOutOfBoundsException()
} else {
src.forEachIndexed { index, value ->
dest[index] = src[index]
}
}
}
星投影
Kotlin 中的星投影指用 *
作为泛型的实参,表示接收任意类型。
相当于 out Any
。
fun getAnimalList(type: Int): MutableList<*> {
if (type == 1) {
return mutableListOf<Animal>(Animal())
} else {
return mutableListOf<Cat>(Cat())
}
}
fun main() {
val animalList = getAnimalList(1)
val catList = getAnimalList(0)
}
等价于:
fun getAnimalList(type: Int): MutableList<out Any> {
if (type == 1) {
return mutableListOf<Animal>(Animal())
} else {
return mutableListOf<Cat>(Cat())
}
}
型变总结
Consumer in, Producer out !大概意思:消费者 in,生产者 out。
- 协变情况:使用 out 关键字,是一种读取行为。泛型作为返回值时。
- 逆变情况:使用 in 关键字,是一种输入行为。泛型作为参数时。
- 特殊场景:同时作为参数和返回值的泛型参数,可以用
@UnsafeVariance
解决冲突。
Kotlin | Java | 边界 | |
---|---|---|---|
协变 | List<out TextView> | List<? extends TextView> | 上限 |
逆变 | List<in TextView> | List<? super TextView> | 下限 |
特殊情况
同时存在out in,官方案例
// 协变
// ↓
public interface List<out E> : Collection<E> {
// 泛型作为返回值
// ↓
public operator fun get(index: Int): E
// 泛型作为参数
// ↓
override fun contains(element: @UnsafeVariance E): Boolean
// 泛型作为参数
// ↓
public fun indexOf(element: @UnsafeVariance E): Int
}
获取泛型参数的类型
可以借助匿名内部类实现,泛型擦除并不是真的将全部的类型信息擦除,而是将类型信息存放在对于class的常量池中。
open class Controller<T> {
}
fun main() {
val controller = object : Controller<Cat>() {}
val superClass = controller.javaClass.genericSuperclass
println(superClass) //com.example.lib_kt.Controller<com.example.lib_kt.Cat>
val type = (superClass as ParameterizedType).actualTypeArguments[0]
println(type) //class com.example.lib_kt.Cat
}
具体化类型参数 reified
在Java中泛型本质都是Object,而在Kotlin中可以通过关键字reified
具体化类型参数
inline fun <reified T : Activity> Activity.startActivity(context: Context) {
startActivity(Intent(context, T::class.java))
}
//使用:
startActivity<TwoActivity>(context)