Kotlin泛型与协变及逆变剖析
关于泛型的使用其实很简单,但是!!如文章开头所说,一直理解不了在Java框架中很多使用泛型都会用<? extends T>和<? super T>
抛开语言层面来对泛型这块的东东彻底把它搞清楚,也就是关于泛型的协变与逆变的概念,下面先从Java的具体泛型的示例中来开始一点点理解它
public class Test {
List<String> list = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
}
但是!!!我们不能这样做:
这是为啥呢?假设这个等式成立的话,那可以这样写:
public static void main(String[] args) { List<String> list = new ArrayList<>(); List<Object> list2 = list; list2.add(new Date()); }
由于list2指向了list,那么我可以以list的角度来取数据,那么就会有:
为了解决这样的问题,Java提供了通配符,如下:
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// List<Object> list2 = new ArrayList<>();
List<? extends Object> list2 = list;
}
}
下面再以集合为例对其进行说明:
集合接口中定义了一个addAll()方法,可以添加现已经存在的集合, 下面定义一个添加方法:
很显然此添加方法在实际需求中是很常见的,这里就可以瞅一下JDK自带的Collection.addAll()方法的定义:
boolean addAll(Collection<? extends E> c);
所以咱们校仿一下:
这样的话,我们就可以很安全的当E类型从集合中去读取,也就是会当Object的类型来读取,所以Collection<String>是Collection<? extends Object>的子类型,这种情况就叫做协变,它只会从集合中当成Object来读取元素,协变是针对读取的;
Collection<String>就是Collection<? extends Object>的子类型。
相反如果这样写的话则是逆变,逆变是针对写入的,如下:
也就是往集合中写元素时必须是String以上的类型,另外需要明白:我们如果只从中读取数据,而不往里面写入内容,那么这样的对象叫做生产者,也就是协变,此时使用 ? extends E;如果只向里面写入数据,而不从中读取数据,那么这样的对象叫做消费者,也就是逆变,使用? super E。
Kotlin中如何来解决协变和逆变的问题
fun main(args: Array<String>) {
var myClass = MyClass<String>("abc")
myTest(myClass)
}
class MyClass< T>(t: T) {
private var t: T
init {
this.t = t
}
fun get(): T = this.t
}
fun myTest(myClass: MyClass<String>) {
var myObject: MyClass<String> = myClass
println(myObject.get())
}
RUN> ??????
abc
接下来要改动程序:
此时可以看到我们定义的泛型,在整个类中只有读取,没有写入,如下:
class MyClass< T>(t: T) {
private var t: T
init {
this.t = t
}
fun get(): T = this.t
}
这其实是协变,这时如果在Java中就可以声明为? extents T,但是在Koltin中用out
它来表示协变:
表示该泛型是要被读的,那如果给T增加一个写方法呢?
所以可见对于协变只能用到读,接下来定义一个逆变:
class MyClass<out T, in M>(t: T, m: M) {
private var t: T
private var m: M
init {
this.t = t
this.m = m
}
fun get(): T = this.t //协变(covariant)只从中读取数据,而不往里面写入内容,那么这样的对象叫做生产者,也就是协变
fun set(m: M) { //逆变(controvariant)只向里面写入数据,而不从中读取数据,那么这样的对象叫做消费者,也就是逆变
this.m = m
}
}
fun myTest(myClass: MyClass<String, Number>) {
var myObject: MyClass<Any, Int> = myClass
println(myObject.get())
}