泛型:参数化类型,可以接收多种不同的类型,来达到代码的通用性。kotlin泛型和Java类似。
优点:
- 类型检查,能在编译时检查错误
- 语义化,直观看到存在什么类型的数据
- 自动类型转换
- 代码的通用性
class Box<T>(t: T) {
var value = t
}
1.Java为什么不能声明一个泛型数组
Apple[] appleArr =new Apple[10]
Fruit[] fruitArray = appleArr
fruitArray[0] = new Banana();//编译通过,运行ArrayStoreException
List<Apple> appleList = new ArrayList<Apple>();
List<Fruit> fruitList = appleList;// 不允许
appleArr可以赋值给fruitArray,但是appleList不能赋值给fruitList。
数组是协变的,list 是不变的。Object[] 是所有对象数组的父亲,而List 却不是List的父亲
但是kotlin的数组是支持泛型的,所以也不再协变
型变
声明处型变(declaration-site variance)与类型投影(type projections)
1.声明处型变out in
在 Kotlin 中,有一种方法向编译器解释这种情况。这称为声明处型变:我们可以标注 Source
的类型参数 T
来确保它仅从 Source<T>
成员中返回(生产),并从不被消费。
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
// ……
}
//简而言之,他们说类 C 是在参数 T 上是协变的,或者说 T 是一个协变的类型参数。 你可以认为 C 是 T 的生产者,而不是 T 的消费者
out修饰符称为型变注解,并且由于它在类型参数声明处提供,所以我们称之为声明处型变。 这与 Java 的使用处型变相反,其类型用途通配符使得类型协变。
除了 out,Kotlin 又补充了一个型变注释:in。它使得一个类型参数逆变:只可以被消费而不可以被生产。逆变类型的一个很好的例子是 Comparable
:
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
// 因此,我们可以将 x 赋给类型为 Comparable <Double> 的变量
val y: Comparable<Double> = x // OK!
}
2.类型投影
fun copy(from: Array<out Any>, to: Array<Any>) { …… }
这里out 称为类型投影:我们说from
不仅仅是一个数组,而是一个受限制的(投影的)数组:我们只可以调用返回类型为类型参数 T
的方法,如上,这意味着我们只能调用 get()
。这就是我们的使用处型变的用法,并且是对应于 Java 的 Array<? extends Object>
、 但使用更简单些的方式。
也可以使用 in 投影一个类型:
fun fill(dest: Array<in String>, value: String) { …… }
Array<in String>
对应于 Java 的 Array<? super String>
,也就是说,你可以传递一个 CharSequence
数组或一个 Object
数组给 fill()
函数。
3.星投影 ????
Kotlin 为此提供了所谓的星投影语法:
- 对于
Foo <out T : TUpper>
,其中T
是一个具有上界TUpper
的协变类型参数,Foo <*>
等价于Foo <out TUpper>
。 这意味着当T
未知时,你可以安全地从Foo <*>
读取TUpper
的值。 - 对于
Foo <in T>
,其中T
是一个逆变类型参数,Foo <*>
等价于Foo <in Nothing>
。 这意味着当T
未知时,没有什么可以以安全的方式写入Foo <*>
。 - 对于
Foo <T : TUpper>
,其中T
是一个具有上界TUpper
的不型变类型参数,Foo<*>
对于读取值时等价于Foo<out TUpper>
而对于写值时等价于Foo<in Nothing>
。
如果泛型类型具有多个类型参数,则每个类型参数都可以单独投影。 例如,如果类型被声明为 interface Function <in T, out U>
,我们可以想象以下星投影:
Function<*, String>
表示Function<in Nothing, String>
;Function<Int, *>
表示Function<Int, out Any?>
;Function<*, *>
表示Function<in Nothing, out Any?>
。
注意:星投影非常像 Java 的原始类型,但是安全。
4、泛型约束
上界
最常见的约束类型是与 Java 的 extends 关键字对应的 上界:
fun <T : Comparable<T>> sort(list: List<T>) { …… }
冒号之后指定的类型是上界:只有 Comparable<T>
的子类型可以替代 T
。 例如:
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型
默认的上界(如果没有声明)是 Any?
。在尖括号中只能指定一个上界。 如果同一类型参数需要多个上界,我们需要一个单独的 where-子句:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
所传递的类型必须同时满足 where
子句的所有条件。在上述示例中,类型 T
必须既实现了 CharSequence
也实现了 Comparable
。
5.类型擦除
Kotlin 为泛型声明用法执行的类型安全检测仅在编译期进行。 运行时泛型类型的实例不保留关于其类型实参的任何信息。 其类型信息称为被擦除。