抬头仰望星空,是否能发现自己的渺小。

伪斜杠青年

人们总是混淆了欲望和理想

泛型声明、本质、意义小结

泛型创建、继承

我们一般看到的泛型类似这样:

public interface Shop<T> {
T buy();

float refund(T item);
}

众所周知,这是一个正确的写法,但也经常出现这样的写法:

public interface CarShop extends Shop<T> {
}

然后IDE提示报错,那么这是为什么呢?先说明,<T>只是一个标识符,在类旁边例如:public class Shop<T>,<T>叫“Type parameter”,用于泛型的创建,表示要创建一个 Shop 类,它的内部会用到⼀个统⼀的类型,这个类型姑且称他为 T ,仅在这个类内部使用。类里面的这个T,可以是任意符合规范的类名称,不和真正存在的冲突即可。

除上述以外其他任何地方的<T>都叫“Type argument ” 比如:Shop<Apple> appleShop; 的 Apple ; 表示那个泛型T,我在这里决定是Apple,这是泛型的实例化。

回到那个问题,原因是,上述写法中<T>实际上不存在。不存在的东西实例化的时候Shop怎么知道是什么呢?有没有想起在泛型外部使用泛型时,都是需要一个真正存在的东西,一个实体类名,比如:new ArrayList<String>()。那么真正的写法应该修改成这样:

public interface CarShop<T> extends Shop<T> {
}

这样就假设了<T>存在于CarShop内部,此时便不会再报错,当然你也可以使用一个真正存在的类型进行替换。

同时泛型代号可以存在多个,像HashMap<K,V>。

泛型参数的边界限制

泛型也有extends关键字,用于限制参数类型,只是与类定义不同需要这样写:

Shop<T extends Car & CarShop & TrainShop>

Car为类,CarShop,TrainShop为接口,类必须放左边第一个,与类继承一致,因为java无法继承多个类,此处便也不能继承多个类。

还有一个关键词super,关于这俩,区别是:

上界用extends关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

下界用super进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object。

协变与逆变

逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)
f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;
f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;
f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。

例:有两个类,关系如下:

class Apple extends Fruit{}
class Fruit{}

协变则需要这样写:

List<Fruit> fruits=new ArrayList<Apple>();//编译错误 java中泛型不可变

解决办法则是通过超类通配符:

List<? extends Fruit> fruits=new ArrayList<Apple>();

逆变理解的写法:

List<Apple> apples=new ArrayList<Fruit>();//编译错误 java中泛型不可变

解决办法:

List<? super Apple> apples=new ArrayList<Fruit>()

这里则引出了一个原则:PECS原则

如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
如果既要存又要取,那么就不要使用任何通配符。

这篇文章解释得还不错:

https://www.jianshu.com/p/2bf15c5265

泛型的意义是方便创造者创造方便使用者的工具,可通过泛型给予友好提示,同时可用于限制方法的参数或者参数关系

泛型擦除

泛型擦除指在代码编译后泛型被丢失,失去了之前对【对象】类型的限制

泛型只在编译时存在,用于帮助开发者减少不必要的错误,编译后如果有上限则为上限,没有则替换为Object,称为类型擦除。原因是为了兼容旧Java版本、减少对象类型(性能)。

实际上编译时生成的class文件可通过反射绕过编译时对类型检测的判断,不过本来就是为了防止错误的,所以没必要。

类型擦除后,怎么获得定义的泛型类型呢?

对于方法:使用Method的getGenericParameterTypes(),或者返回值类型getGenericReturnType()。

对于属性:使用field.getGenericType()。

由于类型擦除,运行时创建的类不会保存泛型信息,比如:

ArrayList<String> list=new ArrayList<String>();

这时候对象并没有class文件产生,仅仅只是调用了构造方法。这时我们需要采取类似Gson的做法,使用子类,大多数情况下是匿名内部类来生成对象,这样便可保留相关泛型信息。例Gson的TypeToken源码:

所以需要改成这样:

ArrayList<String> list=new ArrayList<String>(){};

Kotlin中的区别

kotlin中,? extends用out替代,? super用in替代,原因与其用途有关,一个是只用于出(读取不写入),一个只用于进(写入不读取),kotlin中可在类或者接口声明处进行界限限制,此时使用时所有泛型均已具备限制,避免方法或参数处每次都需要进行限制。kotlin中 * 可解除限制,与java中的原始定义意义一致,表兼容任意类型。

以上为泛型的总结,仅记录理论部分。推荐一篇文章:

https://blog.csdn.net/qq_26222859/article/details/50754675


2条评论

  • 头像

    mason

    鼠标每点击一下都是加密的感觉~你主题换了

    • Mosaic-C

      Mosaic-C

      嗯,换了,所以你是怎么知道的~

发表评论