类比学习——java 泛型& kotlin 泛型 in out where_knight
学习 kotlin 泛型的时候,经常会遇到 in out 这两个词,一会用in 一会用out,为啥这里要用 out ?为啥哪里用 in ?啥什么用 out 啥时候用in ?对应上面问题以前我是晕乎乎的,不是很明白,于是打算写这篇文章梳理一下,搞清楚怎么回事。
Java 泛型
在学kotlin 泛型之前,先回顾一下Java中的泛型
为了方便说明引入下面几个类
具体代码
publicclassAnimal{}publicclassDogextendsAnimal{}publicclassCatextendsAnimal{}publicclassCorgiextendsDog{}publicclassResult<T>{private T data;publicResult(){}publicResult(T data){this.data = data;}publicvoidsetData(T data){this.data = data;}public T getData(){return data;}}
1.不可型变
Result<Dog> dogResult =newResult<>();
Result< Animal> animalResult = dogResult;
虽然 Dog 是 Animal 的子类,但是Java 泛型是不可以型变的,Result<Dog>
对象不能赋值给 Result<Animal>
, 他们之间没有关系。
如果 Java 泛型不是这样设计的就容易造成运行时异常,例如
Result<Dog> dogResult =newResult<>();
Result<Animal> animalResult = dogResult;
animalResult.setData(newAnimal());
Dog dog = dogResult.getData();
所以 Java 这样设计是为了安全考虑。
为了安全,这样的限制显然会失去一些API的灵活性。于是 Java 提供有限制通配符 ? extends X
、? super X
、<?>
来提升API 的灵活性。
2.型变性通配符 —— ? extends
Result<Dog> dogResult =newResult<>(newDog());
Result<?extendsAnimal> animalResult = dogResult;
Animal animal = animalResult.getData();
<? extends X>
可以表示泛型是 X 也可以是 X 的子类,所以上面代码中 Result<? extends Animal> animalResult = dogResult;
是可以的,但是为了安全考虑,animalResult
只可以读数据,不可用写入数据,防止出现类型异常。
<? extends X>
可以安全的使用读取数据(返回值为 X 的函数),但不能写入数据(参数为 X 的函数)因为参数需要的是X 还是 X 的那个子类不确定,就像上面的例子 animalResult
指向的类型是 Result 所以setData 应该放入一个Dog对象,但是 animalResult
也可以指向一个 Result<Cat>
对象,我们只能确定 animalResult
指向的对象泛型是Animal 或者是它的子类,但无法确认它到底是具体哪一个类型(是 Dog 还是Cat 还是……),所以setData的时候我们无法确认到底放那个对象。为了安全考虑,写入数据在这种通配符的情况是不被允许的。
总结 ? extends 通配符
- 可以协变,如上 Cat、Dog、Corgi 都是 Animal 的子类,所以
Result<Cat>
、Result<Dog>
、Result<Corgi>
都可以用Result<? extends Animal>
表示。? extends 通配符 限定了上界,泛型的类型可以是 Animal 或它的子类,但不能超过它,即不能是它的父类。 - 此通配符,只可以读不可用写,这种对象通常称为消费者。
3.逆变型通配符 —— ?super
Result<Dog> dogResult =newResult<>(newDog());
Result<Object> objResult =newResult<>(newObject());
Result<?super Animal> animalResult = objResult;
animalResult.setData(newAnimal());
Object data = animalResult.getData();
<? super X>
可以表示泛型是 X 也可以是 X 的父类,所以上面代码 Result<? super Animal> animalResult = objResult;
是可以的,但 animalResult=dogResult
不可用。此通配符限制了泛型是 X 也可以是 X 的父类,所以通配符,是可以安全的写入数据的(参数为 X 的函数) 但传入的类型必须是 X 或者他的子类,因为 ? super X
可以保证泛型是X 或它的父类,根据类的多态特性,可以使用子类代替父类。如果读的话,无法确认具体的类型,因为只知道是 X 或 它的父类,但具体那个不知道,所有返回的类型是他们顶层父类 Object。
总结 ? super 通配符
- 可以逆变 。? super 通配符 限定了下界,泛型的类型可以是 Animal 或它的父类,但不能低于它,即不能是它的子类。
- 此通配符,可以写,但读无法确定类型,这种对象通常称为生产者。
4.使用通配符限定参数
? extends X 作为参数
例如 Java 中集合框架中的 addAll 方法
publicinterface Collection<E> extends Iterable<E>{
boolean addAll(Collection<? extends E> items);}-----------------------分割线-------------------------
ArrayList<Animal> list = new ArrayList<>();
list.addAll(new ArrayList<Dog>());
list.addAll(new ArrayList<Cat>());
声明的集合类型是 ArrayList 所以此时 addAll 的形参类型相当于是 Collection<? extends Animal>
这样就限定了集合泛型必须是 Animal 或者它的子类,于是就保证了通过addAll 方式添加到 list 集合中的元素一定是 Animal 或者它的子类的对象,保证了数据的正确性。
? super X 作为参数
public void forEach(Consumer<?super E> action){
……
for(int i =0;this.modCount == expectedModCount && i < size;++i){
action.accept(elementAt(es, i));}
……
}-----------------------分割线-------------------------
ArrayList<Animal> list = new ArrayList<>();
list.forEach(new Consumer<Animal>(){@Overridepublic void accept(Animal data){
System.out.println(data);}});
list.forEach(new Consumer<Object>(){@Overridepublic void accept(Object data){
System.out.println(data);}});
在上面代码中 forEach 的形参类型是 Consumer<? super Animal>
, 所以泛型可以值 Animal 或者它的父类,所以不管是 Consumer<Animal>
还是 Consumer<Object>
都是可以的。
kotlin 泛型
kotlin 泛型和 Java 类似,虽然没有 ? extends 和 ?super 这样的通配符 ,但是有类似功能的修饰符 in out
可以简单理解 ?extends 对应 out,? super 对应 in
1.不可型变
val dogResult = Result<Dog>()val animalResult: Result<Animal>= dogResult
和 Java 一致
2.型变修饰符 —— out
val dogResult =Result(Dog())val animalResult: Result<out Animal>= dogResult val animal = animalResult.data
和 Java ?extends 一致,具有型变行,可读不可写
3.型变修饰符 —— int
val dogResult =Result(Dog())val objResult = Result<Any>(Any())val animalResult: Result<in Animal>= objResult
animalResult.data =Animal()valdata:Any?= animalResult.data
和 Java ?super 一致,具有可逆变性,可写不可读
Kotlin 泛型之 —— 声明处型变
在Java 泛型中我们列举了使用通配符限定参数的方式,看了 的Api 是如何利用泛型通配符提升Api灵活性的同时,保证数据安全的。 kotlin 也可以按照 ?extends 替换 out,? super 替换 in 的方式限定参数也是可以达到和Java一样的效果。但是如果你看
的addAll 的方法并不是我们想的那样。
的 addAll 方法
publicbooleanaddAll(Collection<?extendsE> item)
按照上面的分析,我们想的 的addAll 大概是这样的
funaddAll(elements: Collection<out E>): Boolean
但实际源码是这样写的
overridefunaddAll(elements: Collection<E>): Boolean
参数类型是 Collection<E>
而不是 Collection<out E>
虽然没有 out 但参数仍然可以起到限制作用。
val list = mutableListOf<Animal>()
list.addAll(mutableListOf<Dog>())
查看 Collection 你会发现它在定义的时候加上了out
publicinterface Collection<out E>: Iterable<E>{publicval size: Int
……
}
这种在类或接口定义处指定 out ,称之为**声明处型变,**这样声明的接口或类中,只能提供读的方法,不能提供写入的方法,此类型我们打算让他变成具有型变性的。例如我把上面的 Result 改成声明处型变
class Result<T>(vardata: T?=null)
我们加上out 发现报错了,因为 out 只读不能写,所以需要吧var 改成 val
这样我们在使用Result 就是协变的了
val dogResult:Result<Dog>=Result(Dog())val animalResult: Result<Animal>= dogResult val animal = animalResult.data
声明类型的时候 out 就可以不用写了,你要写了人家还提示你多余
说完 out,in 也是同理,我们也可以在类或接口上指定
class Result<in T>{privatevardata: T?=nullfunsetData(data: T?){this.data=data}
编译报错,只写,不能读
}
这样我们在使用Result 就是逆变性的
val objResult: Result<Any>=Result()val animalResult: Result< Animal>= objResult
animalResult.setData(Animal())
kotlin 中的where
看kotlin的wherect之前,还是看看Java中类似的语法
泛型函数
publicstatic<T>voidtest(T data){}-----------------------分割线-------------------------test("");test(newAnimal());
加限制的泛型函数
publicstatic<T extendsAnimal>voidtest(T data){}-----------------------分割线-------------------------test(newDog());test(newAnimal());
加多条限制的泛型函数
publicstatic<T extendsAnimal& Serializable & Closeable>voidtest(T data){}staticclassDataextendsAnimalimplementsSerializable,Closeable{@Overridepublicvoidclose()throws IOException {}}publicstaticvoidmain(String[] args){test(newData());}
Kotlin 中的 where 就是用来实现 Java 中 多条限制的泛型函数
fun <T>test(data: T) where T : Serializable, T : Animal, T : Closeable {}classData:Animal(), Serializable, Closeable {
override fun close(){}}
fun main(){test(Data())}