类比学习——java 泛型& kotlin 泛型 in out where_knight

2021-03-0514:29:08编程语言入门到精通Comments1,969 views字数 5525阅读模式

学习 kotlin 泛型的时候,经常会遇到 in out 这两个词,一会用in 一会用out,为啥这里要用 out ?为啥哪里用 in ?啥什么用 out 啥时候用in ?对应上面问题以前我是晕乎乎的,不是很明白,于是打算写这篇文章梳理一下,搞清楚怎么回事。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

Java 泛型

在学kotlin 泛型之前,先回顾一下Java中的泛型
为了方便说明引入下面几个类
类比学习——java 泛型& kotlin 泛型 in out where_knight文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

具体代码文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

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 泛型不是这样设计的就容易造成运行时异常,例如文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

 Result<Dog> dogResult =newResult<>();
 Result<Animal> animalResult = dogResult;
 animalResult.setData(newAnimal());
 Dog dog = dogResult.getData();

所以 Java 这样设计是为了安全考虑。
为了安全,这样的限制显然会失去一些API的灵活性。于是 Java 提供有限制通配符 ? extends X 、? super X 、<?> 来提升API 的灵活性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

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 只可以读数据,不可用写入数据,防止出现类型异常。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

<? extends X> 可以安全的使用读取数据(返回值为 X 的函数),但不能写入数据(参数为 X 的函数)因为参数需要的是X 还是 X 的那个子类不确定,就像上面的例子 animalResult 指向的类型是 Result 所以setData 应该放入一个Dog对象,但是 animalResult 也可以指向一个 Result<Cat> 对象,我们只能确定 animalResult 指向的对象泛型是Animal 或者是它的子类,但无法确认它到底是具体哪一个类型(是 Dog 还是Cat 还是……),所以setData的时候我们无法确认到底放那个对象。为了安全考虑,写入数据在这种通配符的情况是不被允许的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

总结 ? extends 通配符文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

  1. 可以协变,如上 Cat、Dog、Corgi 都是 Animal 的子类,所以 Result<Cat> 、Result<Dog> 、Result<Corgi> 都可以用 Result<? extends Animal> 表示。? extends 通配符 限定了上界,泛型的类型可以是 Animal 或它的子类,但不能超过它,即不能是它的父类。
  2. 此通配符,只可以读不可用写,这种对象通常称为消费者。

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。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

总结 ? super 通配符文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

  1. 可以逆变 。? super 通配符 限定了下界,泛型的类型可以是 Animal 或它的父类,但不能低于它,即不能是它的子类。
  2. 此通配符,可以写,但读无法确定类型,这种对象通常称为生产者。

4.使用通配符限定参数

? extends X 作为参数

例如 Java 中集合框架中的 addAll 方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

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 或者它的子类的对象,保证了数据的正确性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

? 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> 都是可以的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

kotlin 泛型

kotlin 泛型和 Java 类似,虽然没有 ? extends 和 ?super 这样的通配符 ,但是有类似功能的修饰符 in out
可以简单理解 ?extends 对应 out,? super 对应 in文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

1.不可型变

val dogResult = Result<Dog>()val animalResult: Result<Animal>= dogResult 

和 Java 一致文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

2.型变修饰符 —— out

val dogResult =Result(Dog())val animalResult: Result<out Animal>= dogResult val animal = animalResult.data

和 Java ?extends 一致,具有型变行,可读不可写文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

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 一致,具有可逆变性,可写不可读文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

Kotlin 泛型之 —— 声明处型变

在Java 泛型中我们列举了使用通配符限定参数的方式,看了 的Api 是如何利用泛型通配符提升Api灵活性的同时,保证数据安全的。 kotlin 也可以按照 ?extends 替换 out,? super 替换 in 的方式限定参数也是可以达到和Java一样的效果。但是如果你看 的addAll 的方法并不是我们想的那样。
的 addAll 方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

publicbooleanaddAll(Collection<?extendsE> item)

按照上面的分析,我们想的 的addAll 大概是这样的文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

funaddAll(elements: Collection<out E>): Boolean

但实际源码是这样写的文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

overridefunaddAll(elements: Collection<E>): Boolean

参数类型是 Collection<E> 而不是 Collection<out E> 虽然没有 out 但参数仍然可以起到限制作用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

val list = mutableListOf<Animal>()
    list.addAll(mutableListOf<Dog>())

查看 Collection 你会发现它在定义的时候加上了out文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

publicinterface Collection<out E>: Iterable<E>{publicval size: Int
	……
}

这种在类或接口定义处指定 out ,称之为**声明处型变,**这样声明的接口或类中,只能提供读的方法,不能提供写入的方法,此类型我们打算让他变成具有型变性的。例如我把上面的 Result 改成声明处型变文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

class Result<T>(vardata: T?=null)

类比学习——java 泛型& kotlin 泛型 in out where_knight文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

我们加上out 发现报错了,因为 out 只读不能写,所以需要吧var 改成 val文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

类比学习——java 泛型& kotlin 泛型 in out where_knight文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

这样我们在使用Result 就是协变的了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

val dogResult:Result<Dog>=Result(Dog())val animalResult: Result<Animal>= dogResult val animal = animalResult.data

声明类型的时候 out 就可以不用写了,你要写了人家还提示你多余类比学习——java 泛型& kotlin 泛型 in out where_knight文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

说完 out,in 也是同理,我们也可以在类或接口上指定文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

class Result<in T>{privatevardata: T?=nullfunsetData(data: T?){this.data=data}

    编译报错,只写,不能读
   }

这样我们在使用Result 就是逆变性的文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

val objResult: Result<Any>=Result()val animalResult: Result< Animal>= objResult 
    animalResult.setData(Animal())

kotlin 中的where

看kotlin的wherect之前,还是看看Java中类似的语法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

泛型函数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

publicstatic<T>voidtest(T data){}-----------------------分割线-------------------------test("");test(newAnimal());

加限制的泛型函数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

publicstatic<T extendsAnimal>voidtest(T data){}-----------------------分割线-------------------------test(newDog());test(newAnimal());

加多条限制的泛型函数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

publicstatic<T extendsAnimal& Serializable & Closeable>voidtest(T data){}staticclassDataextendsAnimalimplementsSerializable,Closeable{@Overridepublicvoidclose()throws IOException {}}publicstaticvoidmain(String[] args){test(newData());}

Kotlin 中的 where 就是用来实现 Java 中 多条限制的泛型函数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html

fun <T>test(data: T) where T : Serializable, T : Animal, T : Closeable {}classData:Animal(), Serializable, Closeable {
    override fun close(){}}

fun main(){test(Data())}
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/21035.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/21035.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定