泛型的目的
在编译阶段完成类型的转换的工作,避免在运行时强制类型转换而出现ClassCastException,类型转化异常。
泛型的使用
泛型类
将泛型定义在类上
public class 类名 <泛型类型1,...> {
}
需要注意的点:
1、泛型在创建对象时,没有指定具体的数据类型时,按照object
2、泛型不支持基本数据类型
3、同一泛型根据 不同的数据类型创建的对象本质上是同一类型
ArrayList<String> list1 = new ArrayList()
ArrayList<Integer> list2 = new ArrayList()
list1.getClass == list2.getClass //true
造成这样的原因是泛型擦除。
4、子类是泛型,子类要和父类的泛型类一致
5、子类不是泛型,父类则需要传入明确的泛型类型
错误的方式:
public class A extends Container<K, V> {}
正确的方式:
public class A extends Container<Integer, String> {}
也可以不指定具体的类型,系统就会把K,V形参当成Object类型处理
public class A extends Container {}
泛型方法
把泛型定义在方法上
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
}
泛型接口
把泛型定义在接口
public interface 接口名<泛型类型> {
}
需要注意的是:
1、实现类不是泛型类,接口需要明确数据类型
2、实现类是泛型类,接口和实现类的泛型是一致
通配符
理解通配符,需要知道一句话,==容器中装的东西有继承关系,但容器之间是没有继承关系的。==
上界通配符:<? extends T>
例如Plate<? extends Fruits> Fruits是所有的上界,只要是Fruits的子类都满足要求。即继承Fruits或者实现Fruits的子类都满足要求。
有一个缺点,只能往外面取东西,不能往里面存东西,即get有效,set会报错。原因是编译器只知道容器内是Fruit或者它的派生类,所以取时不能用具体的派生类接收。只能用Fruit或者Fruit的基类接收。
上界通配符Plate<? extends Fruit>里面放什么不确定,都是Fruit的派生类。存
操作时,接收却只是用了一个占位符或者通配符来接收,来表示捕获一个Fruit类或其子类,具体什么类不知道。然后无论是想往里面插入Apple或者Meat或者Fruit编译器都不知道能不能和这个占位符匹配,所以存操作都不允许。
下界通配符<? super T>
例如Plate<? super Fruits> 其下界就是Fruits,所以Fruits是最低的,只能是Fruits的父类才能满足条件
有一个缺点,下界<? super T>不影响往里面存,但是往外取时只能放到Object对象里面去,不能放到具体的对象中去
因为下界规定了元素的最小粒度的下限,实际上是放松可容器元素的类型控制。因为存放的元素都是Fruit的基类,那只
要往里面存的粒度都比Fruit小或等都可以。但往外读取元素就费劲了,只有所有类的基类Objeect对象才能保证每次都装下。
但是这样的话,元素的类型信息就全部丢失了。
PECS原则
PECS的意思是Producer Extend Consumer Super,简单理解为如果是生产者则使用Extend,如果是消费者则使用Super
PECS是从集合的角度出发的
1.如果你只是从集合中取数据,那么它是个生产者,你应该用extend
2.如果你只是往集合中加数据,那么它是个消费者,你应该用super
3.如果你往集合中既存又取,那么你不应该用extend或者super
优点:
使用PECS主要是为了实现集合的多态
泛型擦除
Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程就是泛型擦除。
既然有泛型擦除,那像retrofit怎么获取类型?
要了解这个,需要先了解Type与泛型的关系。
Type
public interface Type {
default String getTypeName() {
return toString();
}
}
Type 接口有一个我们熟悉的实现类 Class 类, 还有四个接口类型TypeVariable、ParameterizedType、WildcardType、GenericArrayType。
TypeVariable
泛型参数 T,TypeVariable 完全就是变量如:T 而不是一种确切的类型。
public interface TypeVariable<D extends GenericDeclaration> extends Type/*, AnnotatedElement*/ {
//返回泛型参数的上界列表, 泛型的声明只有 extends 形式
Type[] getBounds();
//泛型参数是在哪里形式声明的,Method 、Constructor、Class
D getGenericDeclaration();
String getName();
}
ParameterizedType
参数化类型
public interface ParameterizedType extends Type {
// 获取 < > 内的 参数信息 。如HashMap< String, T> 中的 String 和 T 。
// String 是 class 类型 ,T 是TypeVariable 类型
Type[] getActualTypeArguments();
// 获取原生类型 ,如 List<T> 的 List 类型它属于 Class 类型。
Type getRawType();
// 获取外部类的类型,如果没有外部类,返回Null
Type getOwnerType();
}
WildcardType
带通配符的类型 。也就是 HashMap<? extends CharSequence, T> 的第一个泛型参数 ? extends CharSequence 。
它的作用和 TypeVariable 一样,是用来描述参数信息的, 和原生类型无关。
public interface WildcardType extends Type {
// 获取 extends 的类型
Type[] getUpperBounds();
//获取 super 的类型
Type[] getLowerBounds();
}
GenericArrayType
public interface GenericArrayType extends Type {
// 获取数组的类型
Type getGenericComponentType();
}
而我们的retrofit就是根据调用method的getGenericReturnType()来获取
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
Type adapterType;
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType = Utils.getParameterLowerBound(0,
(ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
// TODO figure out if type is nullable or not
// Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
// Find the entry for method
// Determine if return type is nullable or not
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();//1
}
getGenericReturnType 获取带泛型信息的返回类型 、
getGenericParameterTypes 获取带泛型信息的参数类型。
虽然在编译时,泛型的信息会被擦除,但是某些(声明侧的泛型,接下来解释) 泛型信息会被class文件 以Signature的形式 保留在Class文件的Constant pool中。
通过javap命令 可以看到在Constant pool中#5 Signature记录了泛型的类型。
Constant pool:
#1 = Class #16 // com/example/diva/leet/GitHubService
#2 = Class #17 // java/lang/Object
#3 = Utf8 listRepos
#4 = Utf8 (Ljava/lang/String;)Lretrofit2/Call;
#5 = Utf8 Signature
#6 = Utf8 (Ljava/lang/String;)Lretrofit2/Call<Ljava/util/List<Lcom/example/diva/leet/Repo;>;>;
#7 = Utf8 RuntimeVisibleAnnotations
#8 = Utf8 Lretrofit2/http/GET;
#9 = Utf8 value
#10 = Utf8 users/{user}/repos
#11 = Utf8 RuntimeVisibleParameterAnnotations
#12 = Utf8 Lretrofit2/http/Path;
#13 = Utf8 user
#14 = Utf8 SourceFile
#15 = Utf8 GitHubService.java
#16 = Utf8 com/example/diva/leet/GitHubService
#17 = Utf8 java/lang/Object
{
public abstract retrofit2.Call<java.util.List<com.example.diva.leet.Repo>> listRepos(java.lang.String);
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #6 // (Ljava/lang/String;)Lretrofit2/Call<Ljava/util/List<Lcom/example/diva/leet/Repo;>;>;
RuntimeVisibleAnnotations:
0: #8(#9=s#10)
RuntimeVisibleParameterAnnotations:
parameter 0:
0: #12(#9=s#13)
}
声明侧泛型会被记录在 Class 文件的 Constant pool 中 :
泛型类,或泛型接口的声明
带有泛型参数的方法
带有泛型参数的成员变量
而使用侧泛型则不会被记录 :也就是方法的局部变量, 方法调用时传入的变量。
比如像Gson解析时传入的参数属于使用侧泛型,因此不能通过Signature解析
Gson是如何获取到List<String>的泛型信息String的呢?
Class类提供了一个方法public Type getGenericSuperclass() ,可以获取到带泛型信息的父类Type。
也就是说java的class文件会保存继承的父类或者接口的泛型信息。
所以Gson使用了一个巧妙的方法来获取泛型类型:
1.创建一个泛型抽象类TypeToken <T> ,这个抽象类不存在抽象方法,因为匿名内部类必须继承自抽象类或者接口。所以才定义为抽象类。
2.创建一个 继承自TypeToken的匿名内部类, 并实例化泛型参数TypeToken<String>
3.通过class类的public Type getGenericSuperclass()方法,获取带泛型信息的父类Type,也就是TypeToken<String>
总结:Gson利用子类会保存父类class的泛型参数信息的特点。 通过匿名内部类实现了泛型参数的传递。
参考文献: