public interface GitHubService {
@GET(“users/{user}/repos”)
Call<List> listRepos(@Path(“user”) String user);
}
使用jad
查看反编译后的class
文件:
import retrofit2.Call;
public interface GitHubService
{
public abstract Call listRepos(String s);
}
可以看到class
文件中已经将泛型信息给擦除了,那么Retrofit
是如何拿到Call<List>
的类型信息的?
我们看一下retrofit
的源码
static ServiceMethod parseAnnotations(Retrofit retrofit, Method method) {
…
Type returnType = method.getGenericReturnType();
…
}
public Type getGenericReturnType() {
// 根据 Signature 信息 获取 泛型类型
if (getGenericSignature() != null) {
return getGenericInfo().getReturnType();
} else {
return getReturnType();
}
}
可以看出,retrofit
是通过getGenericReturnType
来获取类型信息的
jdk
的Class
、Method
、Field
类提供了一系列获取 泛型类型的相关方法。
以Method
为例,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)
}
这就是我们retrofit
中能够获取泛型类型的原因
2.4 Gson
解析为什么要传入内部类
Gson
是我们常用的json
解析库,一般是这样使用的
// Gson 常用的情况
public List parse(String jsonStr){
List topNews = new Gson().fromJson(jsonStr, new TypeToken<List>() {}.getType());
return topNews;
}
我们这里可以提出两个问题
1.Gson
是怎么获取泛型类型的,也是通过Signature
吗?
2.为什么Gson
解析要传入匿名内部类?这看起来有些奇怪
2.4.1 那些泛型信息会被保留,哪些是真正的擦除了?
上面我们说了,声明侧泛型会被记录在Class
文件的Constant pool
中,使用侧泛型则不会
声明侧泛型主要指以下内容
1.泛型类,或泛型接口的声明 2.带有泛型参数的方法 3.带有泛型参数的成员变量
使用侧泛型
也就是方法的局部变量,方法调用时传入的变量。
Gson
解析时传入的参数属于使用侧泛型,因此不能通过Signature
解析
2.4.2 为什么Gson
解析要传入匿名内部类
根据以上的总结,方法的局部变量的泛型是不会被保存的
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
的泛型参数信息的特点。 通过匿名内部类实现了泛型参数的传递。
3.什么是PECS
原则?
3.1 PECS
介绍
PECS
的意思是Producer Extend Consumer Super
,简单理解为如果是生产者则使用Extend
,如果是消费者则使用Super
,不过,这到底是啥意思呢?
PECS
是从集合的角度出发的
1.如果你只是从集合中取数据,那么它是个生产者,你应该用extend
2.如果你只是往集合中加数据,那么它是个消费者,你应该用super
3.如果你往集合中既存又取,那么你不应该用extend
或者super
让我们通过一个典型的例子理解一下到底什么是Producer
和Consumer
public class Collections {
public static void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++) {
dest.set(i, src.get(i));
}
}
}
上面的例子中将src
中的数据复制到dest
中,这里src
就是生产者,它「生产」数据,dest
是消费者,它「消费」数据。
3.2 为什么需要PECS
使用PECS
主要是为了实现集合的多态
举个例子,现在有这样一个需求,将水果篮子中所有水果拿出来(即取出集合所有元素并进行操作)
public static void getOutFruits(List basket){
for (Fruit fruit : basket) {
System.out.println(fruit);
//…do something other
}
}
List fruitBasket = new ArrayList();
getOutFruits(fruitBasket);//成功
List appleBasket = new ArrayList();
getOutFruits(appleBasket);//编译错误
如上所示:
1.将List<Apple>
传递给List<Fruit>
会编译错误。
2.因为虽然Fruit
是Apple
的父类,但是List<Apple>
和List<Fruit>
之间没有继承关系
3.因为这种限制,我们不能很好的完成取出水果篮子中的所有水果需求,总不能每个类型都写一遍一样的代码吧?
使用extend
可以方便地解决这个问题