泛型创建、继承
我们一般看到的泛型类似这样:
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中的原始定义意义一致,表兼容任意类型。
以上为泛型的总结,仅记录理论部分。推荐一篇文章:
本站由以下主机服务商提供服务支持:
mason
鼠标每点击一下都是加密的感觉~你主题换了
Mosaic-C
嗯,换了,所以你是怎么知道的~