Kotlin不变型泛型
Kotlin的不变型泛型和Java一样,通过声明泛型类型来使用泛型类。而该种泛型声明后,则无法使用父类方法与属性。在编译时候,会将泛型擦除。
代码语言:javascript复制fun test() {
val m = Noob<Int>()
val n = Noob<String>()
}
class Noob<T> {
var a: T? = null
fun print() {
println(a)
}
}
泛型的单继承关系
如果使用单继承关系的话,也和Java相同,在泛型定义时,使用继承即可
代码语言:javascript复制fun test() {
val m = Noob<Double>()
val n = Noob<Float>()
}
// 指定泛型T是Number的子类
class Noob<T : Number> {
var a: T? = null
fun print() {
println(a?.toInt())
}
}
泛型的多继承关系
当泛型需要使用多继承关系的话,则可以使用where
子句来约束该泛型的每一个子类。
class Noob<T> where T : Number, T : Comparable<T> {
lateinit var a: T
fun compare(c: T?): Int {
print(c?.toInt())
return c?.compareTo(a) ?: 0
}
}
协变(Covariant)与逆变(Contravariance)
在Java中同样也有这两种类型变换。对于Java而言,也就是:
- 协变:
List<? extends String>
- 逆变:
List<? super String>
通过继承,我们可以让A继承B,而协变可以理解成A->B,而逆变则可以理解成B->A。
在Java中,可能会出现这种情况:
代码语言:javascript复制List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // 编译出错!!!即将来临的问题的原因就在这里。Java 禁止这样!
objs.add(1); // 这里我们把一个整数放入一个字符串列表
String s = strs.get(0); // !!! ClassCastException:无法将整数转换为字符串
而一般认为,String是Object的子类,而List<Object> objs = strs
理论上是正确的,但是编译会出错,因为List的泛型是不变型的,也就是定义的泛型是Object
就必须是Object
,而不能是它的子类。
而这极大的限制了程序的灵活性。于是就产生了协变与逆变。
首先看一下协变:
代码语言:javascript复制class A{}
class B extends A{}
List<B> listB = new ArrayList();
List<? extends B> objList = listB; // 正常编译
objList.add(new B()); // 编译出错
B m = objList.get(0); // 正常编译
通过List<? extends B>
定义了协变,允许从列表中获取的对象都可以转换成B
的引用,但是不允许往该列表中添加对象。
在看一下逆变:
代码语言:javascript复制class A{}
class B extends A{}
List<A> listA = new ArrayList();
List<? super A> objList = listA; // 正常编译
objList.add(new B()); // 正常编译
A m = objList.get(0); // 编译出错
通过List<? super A>
定义了逆变,允许向列表中添加以A
为父类的B类对象,而不允许从列表中获取对象。
通过协变与逆变的方式,在保证代码灵活性的同时,也定义了代码的上下边界,保证代码的安全性。
Kotlin中的协变与逆变
前人总结过使用协变与逆变的时机,即:PECS
。也就是:
- Product Extends,Consumer Super
也就是,当你使用它来向外输出数据时,可认为它是Productor,则需要使用Extends
,而当使用它来接收外部数据时,则可认为它是Consumer,则需要使用Super
。
从上例可以看到,协变可以从
objList
中成功获取B
对象,说明此时objList
则是作为Productor向外部输出数据,所以需要使用extends
。而逆变可以允许objList
中添加B
对象,则可认为此时objList
是作为Consumer来消费外部传入的数据。
而在Kotlin中使用out
和in
来实现协变与逆变。
首先定义三个类,递增继承。
代码语言:javascript复制open class Man(var age: Int)
open class YoungMan(age: Int, var school: String) : Man(age)
class OldMan(age: Int, school: String, var company: String) : YoungMan(age, school)
Kotlin的协变,也就是Productor,使用out
来限制不允许输入:
val youngManList: MutableList<out YoungMan> = ArrayList()
youngManList.add(YoungMan(10, "")) // 编译出错,限制往列表中添加元素
val x: YoungMan = youngManList.get(0) // 编译通过
而逆变,也就是Consumer,则使用in
来限制不允许输出:
val youngManList: MutableList<in YoungMan> = ArrayList()
youngManList.add(YoungMan(10, "")) //编译通过
val x: YoungMan = youngManList.get(0) // 编译出错,限制从列表中获取元素
而在普通的类中使用也是同样的效果,当使用in
时:
class MyClass<in T> where T : Number {
fun printT(x: T) { // 编译通过
x.toLong()
}
fun getT(): T? { // 编译错误,
return null
}
}
当使用out
时:
class MyClass<out T> where T : Number {
fun printT(x: T) { // 编译错误
x.toLong()
}
fun getT(): T? { // 编译通过
return null
}
}