Kotlin Reference (十一) 泛型、数组型变、泛型型变、泛型约束
官方在线Reference
kotlin-docs.pdf
Kotlin for android Developers 中文翻译
Kotlin开发工具集成,相关平台支持指南
Kotlin开源项目与Libraries
Kotlin开源项目、资源、书籍及课程搜索平台
Google’s sample projects written in Kotlin
Kotlin and Android
Kotlin作为一门基于 JVM 的新的编程语言,它也支持泛型类型:
class Box<T>(t: T) {
var value = t
}
定义一个使用泛型参数的实例:
val box: Box<Int> = Box<Int>(1)
如果,声明的是一个类型自动推测的表达式,那么可以省略泛型参数:
val box = Box(1)
型变特性,分变和不变;变又分协变与逆变
简单的描述 协变与逆变,它们有一个共同的前提,即出现在”有继承或实现”关系的一组类型中。
协变:父类出现的地方,可以用子类代替(符合面向对象的基本原则,里氏替换原则;比如方法返回类型是一个基类型,返回值是一个子类型对象,这就是一种协变)
逆变:子类出现的地方,可以用父类代替
不变:声明的是什么类型,使用或传递的就要是什么类型
注:以上只是协变与逆变的基本概念,在泛型中使用这两个特性,还有其它要注意的地方。
java中数组是协变的
数组在java中,默认就是协变的
如下代码,在java中允许的:
Integer[] iAry = new Integer[] {33};
Object[] objects = iAry;
若将iAry改一下:
List<String>[] iAry = new List[1];
Object[] objects = iAry
objects[0] = 3;
运行后,会发生异常。因objects[0] 实际存储的是一个List类型的元素,而这里被赋值为一个Integer型。
Kotlin中的数组
在Kotlin中的数组,sdk定义了一个Array<T>这样的数组类型,任何kotlin数组,都继承了它。所以,Kotlin中数组变异,遵循泛型变异的特性。
泛型不变
比如,
List<String> = new ArrayList<String>();
Box<Integer> = new Box<Integer>();
声明泛型参数类型,和赋值的泛型参数类型需要相同。
若
List<String> = new ArrayList<Integer>();
Box<Integer> = new Box<String>();
这样肯定编译不过的
java泛型协变
例如,
// Java
interface ACollection<E> {
void addAll(ACollection<E> items);
}
class Test {
void copyAll(ACollection<Object> to, ACollection<String> from) {
to.addAll(from); //error
}
}
根据泛型不变特性,这里的 to.addAll(from);
是无法编译通过的。
现在,做如下修改:
// Java
interface ACollection<E> {
void addAll(ACollection<? extends E> items);
}
这里方法中,声明泛型参数时,加上了 “? extends” 这样的通配符与关键字。
直译就是,E的子类/实现类。 匹配到copyAll方法的 to.addAll
方法中,就是
ACollection<Object> {
void addAll(ACollection<String extends Object> items);
}
加上了 “? extends” 这样的通配符定义后,就为对应的泛型参数的类型,指定了上限
再来看个例子:
List<? extends Object> list = new ArrayList<>();
list.add("abc");//error
这里的list.add()
的完全语义:list.add(? extends Object)
,示例中的类型完全是符合的,但是在编译器中,直接报错了。
本例,与上一个例子的区别在哪里呢:
上例
// Java
interface ACollection<E> {
void addAll(ACollection<? extends E> items);
}
在接口中的方法定义上使用了协变语法,而在后期使用addAll()
的对象,是已经确定泛型参数类型的对象:ACollection<Object>
和ACollection<String>
而本例中的list对象,它直接就是一个协变语法定义的对象:List<? extends Object> list
;对应的,它的add()的语义:add(? extends Object)
这里的理解:list不知道它的具体泛型参数类型是Object,还是Object的某个子类型,而这样一个不知道它关联哪种具体类型的集合对象list,又要add一个不知道类型的对象,所以这是不支持的
Kotlin泛型协变
Kotlin中协变语义的声明位置与java中不完全相同,可声明在 类或接口 的定义上,
如:
class Box3<in T, E> {
fun copy(from: Array<out E>, to: Array<E>) {
for (index in from.indices) {
to[index] = from[index]
}
}
}
kotlin中,引用了生产者-消费者的概念来对应 泛型协变与逆变。
协变,对应生产者。即相关函数调用,是产生一个与泛型类型同类型的对象。
如果将上面to: Array<E>
定义成 to: Array<out E>
那就会报错了。
因to[index]
在等式的左边,它不是一个被直接用来产生对象的函数
结论:协变 — out — 生产者 — 直接产生泛型类型对象
java中泛型逆变
例,
void add(List<? super Integer> foo) {
foo.add(3); //只能是integer
// foo.add(new Object());//报错, 只能是integer
}
这里理解:foo对象不知道要放什么样的Integer的超类型对象,所以只能放Integer对象。
泛型逆变,确定泛型参数类型的下限
如上例,再加一句代码:
Integer a = foo.get(0);//error, 编译 不通过
因foo的定义,它不知道泛型参数对应Integer的何种基类型,所以调用get(),就不知道返回的是哪种类型了。
Kotlin泛型逆变
例,
fun copy2(from: Array<in Any>, to: Array<Any>) {
for (index in from.indices) {
// to[index] = from[index] //error
from[index] = to[index]
}
}
这里的 <in T>
关键字,就表示 这是一个逆变的泛型声明;类中所有用到T类型的地方,都要遵循逆变的特性。即支持消费对象,而不支持生产对象
结论:逆变 — in — 消费者 — 用于消费对象或者写入对象
泛型参数定义在其它位置
- 在top级函数中使用泛型
即在kotlin-file中的,最外层使用泛型,如
文件Test.kt下:
fun <T> get(t: T): T {
return t
}
直接在top级函数中使用泛型, 这里的泛型不能 定义为 in 或 out
- 直接定义在类或接口声明中
如下,
interface AG<T> {} //这时是可以带型变关键字的
若无型变关键字,表示,对与 协变与逆变 都支持
interface MyFun<in T, out U> {
fun testp(t: T): U
fun inParam(t: T)
fun outValue(): U
}
fun test11(m: MyFun<*, String>) {//in 声明成*, 表示 in Nothing 即不能写入任何东西,因为此时不知道*是什么类型的
// m.testp()
// m.inParam()
m.outValue()
}
fun test12(m: MyFun<Int, *>) {//out 声明成*,表示 out U
val result = m.testp(33)
println("test12: " + result)
m.inParam(34)
m.outValue()
}
fun test13(m: MyFun<*, *>) {// <in Nothing, out U>
// m.testp()
// m.inParam()
m.outValue()
}
当不知道in的类型时,用 *表示 in类型, 可以安全地 防止 写入
out是读取的动作,用*表示,与原意一样
如下定义一个普通的泛型函数:
fun <T> singletonList(item: T): List<T> {
// ...
}
利用泛型参数,进行函数扩展:
fun <T> T.basicToString() : String { // extension function
// ...
}
调用泛型函数:
val l = singletonList<Int>(1)
val s = singletonList(1)//泛型类型自动推断
- 最常见的一种约束是一个上边界约束, 对应于Java的extends关键字:
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}
<T : Comparable<T>>
表示T要继承或实现自Comparable<T>
sort函数调用:
sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int> sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
- 指定空类型上边界
fun <T: Any?> test():T? {
return null //即使上边界为Any?,在使用时也要加上 ?
}
- 对同一个泛型参数,指定多个上边界
在声明泛型参数时,只能指定一个上边界,若要指定多个,需要用where关键字:
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
where T : Comparable,
T : Cloneable {
return list.filter { it > threshold }.map { it.clone() }
}