泛型的目的
在編譯階段完成類型的轉(zhuǎn)換的工作,避免在運(yùn)行時(shí)強(qiáng)制類型轉(zhuǎn)換而出現(xiàn)ClassCastException,類型轉(zhuǎn)化異常。
泛型的使用
泛型類
將泛型定義在類上
public class 類名 <泛型類型1,...> {
}
需要注意的點(diǎn):
1、泛型在創(chuàng)建對(duì)象時(shí),沒有指定具體的數(shù)據(jù)類型時(shí),按照object
2、泛型不支持基本數(shù)據(jù)類型
3、同一泛型根據(jù) 不同的數(shù)據(jù)類型創(chuàng)建的對(duì)象本質(zhì)上是同一類型
ArrayList<String> list1 = new ArrayList()
ArrayList<Integer> list2 = new ArrayList()
list1.getClass == list2.getClass //true
造成這樣的原因是泛型擦除。
4、子類是泛型,子類要和父類的泛型類一致
5、子類不是泛型,父類則需要傳入明確的泛型類型
錯(cuò)誤的方式:
public class A extends Container<K, V> {}
正確的方式:
public class A extends Container<Integer, String> {}
也可以不指定具體的類型,系統(tǒng)就會(huì)把K,V形參當(dāng)成Object類型處理
public class A extends Container {}
泛型方法
把泛型定義在方法上
public <泛型類型> 返回類型 方法名(泛型類型 變量名) {
}
泛型接口
把泛型定義在接口
public interface 接口名<泛型類型> {
}
需要注意的是:
1、實(shí)現(xiàn)類不是泛型類,接口需要明確數(shù)據(jù)類型
2、實(shí)現(xiàn)類是泛型類,接口和實(shí)現(xiàn)類的泛型是一致
通配符
理解通配符,需要知道一句話,==容器中裝的東西有繼承關(guān)系,但容器之間是沒有繼承關(guān)系的。==
上界通配符:<? extends T>
例如Plate<? extends Fruits> Fruits是所有的上界,只要是Fruits的子類都滿足要求。即繼承Fruits或者實(shí)現(xiàn)Fruits的子類都滿足要求。
有一個(gè)缺點(diǎn),只能往外面取東西,不能往里面存東西,即get有效,set會(huì)報(bào)錯(cuò)。原因是編譯器只知道容器內(nèi)是Fruit或者它的派生類,所以取時(shí)不能用具體的派生類接收。只能用Fruit或者Fruit的基類接收。
上界通配符Plate<? extends Fruit>里面放什么不確定,都是Fruit的派生類。存
操作時(shí),接收卻只是用了一個(gè)占位符或者通配符來接收,來表示捕獲一個(gè)Fruit類或其子類,具體什么類不知道。然后無論是想往里面插入Apple或者M(jìn)eat或者Fruit編譯器都不知道能不能和這個(gè)占位符匹配,所以存操作都不允許。
下界通配符<? super T>
例如Plate<? super Fruits> 其下界就是Fruits,所以Fruits是最低的,只能是Fruits的父類才能滿足條件
有一個(gè)缺點(diǎn),下界<? super T>不影響往里面存,但是往外取時(shí)只能放到Object對(duì)象里面去,不能放到具體的對(duì)象中去
因?yàn)橄陆缫?guī)定了元素的最小粒度的下限,實(shí)際上是放松可容器元素的類型控制。因?yàn)榇娣诺脑囟际荈ruit的基類,那只
要往里面存的粒度都比Fruit小或等都可以。但往外讀取元素就費(fèi)勁了,只有所有類的基類Objeect對(duì)象才能保證每次都裝下。
但是這樣的話,元素的類型信息就全部丟失了。
PECS原則
PECS的意思是Producer Extend Consumer Super,簡單理解為如果是生產(chǎn)者則使用Extend,如果是消費(fèi)者則使用Super
PECS是從集合的角度出發(fā)的
1.如果你只是從集合中取數(shù)據(jù),那么它是個(gè)生產(chǎn)者,你應(yīng)該用extend
2.如果你只是往集合中加數(shù)據(jù),那么它是個(gè)消費(fèi)者,你應(yīng)該用super
3.如果你往集合中既存又取,那么你不應(yīng)該用extend或者super
優(yōu)點(diǎn):
使用PECS主要是為了實(shí)現(xiàn)集合的多態(tài)
泛型擦除
Java的泛型基本上都是在編譯器這個(gè)層次上實(shí)現(xiàn)的,在生成的字節(jié)碼中是不包含泛型中的類型信息的,使用泛型的時(shí)候加上類型參數(shù),在編譯器編譯的時(shí)候會(huì)去掉,這個(gè)過程就是泛型擦除。
既然有泛型擦除,那像retrofit怎么獲取類型?
要了解這個(gè),需要先了解Type與泛型的關(guān)系。
Type
public interface Type {
default String getTypeName() {
return toString();
}
}
Type 接口有一個(gè)我們熟悉的實(shí)現(xiàn)類 Class 類, 還有四個(gè)接口類型TypeVariable、ParameterizedType、WildcardType、GenericArrayType。
TypeVariable
泛型參數(shù) T,TypeVariable 完全就是變量如:T 而不是一種確切的類型。
public interface TypeVariable<D extends GenericDeclaration> extends Type/*, AnnotatedElement*/ {
//返回泛型參數(shù)的上界列表, 泛型的聲明只有 extends 形式
Type[] getBounds();
//泛型參數(shù)是在哪里形式聲明的,Method 、Constructor、Class
D getGenericDeclaration();
String getName();
}
ParameterizedType
參數(shù)化類型
public interface ParameterizedType extends Type {
// 獲取 < > 內(nèi)的 參數(shù)信息 。如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> 的第一個(gè)泛型參數(shù) ? extends CharSequence 。
它的作用和 TypeVariable 一樣,是用來描述參數(shù)信息的, 和原生類型無關(guān)。
public interface WildcardType extends Type {
// 獲取 extends 的類型
Type[] getUpperBounds();
//獲取 super 的類型
Type[] getLowerBounds();
}
GenericArrayType
public interface GenericArrayType extends Type {
// 獲取數(shù)組的類型
Type getGenericComponentType();
}
而我們的retrofit就是根據(jù)調(diào)用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 獲取帶泛型信息的參數(shù)類型。
雖然在編譯時(shí),泛型的信息會(huì)被擦除,但是某些(聲明側(cè)的泛型,接下來解釋) 泛型信息會(huì)被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)
}
聲明側(cè)泛型會(huì)被記錄在 Class 文件的 Constant pool 中 :
泛型類,或泛型接口的聲明
帶有泛型參數(shù)的方法
帶有泛型參數(shù)的成員變量
而使用側(cè)泛型則不會(huì)被記錄 :也就是方法的局部變量, 方法調(diào)用時(shí)傳入的變量。
比如像Gson解析時(shí)傳入的參數(shù)屬于使用側(cè)泛型,因此不能通過Signature解析
Gson是如何獲取到List<String>的泛型信息String的呢?
Class類提供了一個(gè)方法public Type getGenericSuperclass() ,可以獲取到帶泛型信息的父類Type。
也就是說java的class文件會(huì)保存繼承的父類或者接口的泛型信息。
所以Gson使用了一個(gè)巧妙的方法來獲取泛型類型:
1.創(chuàng)建一個(gè)泛型抽象類TypeToken <T> ,這個(gè)抽象類不存在抽象方法,因?yàn)槟涿麅?nèi)部類必須繼承自抽象類或者接口。所以才定義為抽象類。
2.創(chuàng)建一個(gè) 繼承自TypeToken的匿名內(nèi)部類, 并實(shí)例化泛型參數(shù)TypeToken<String>
3.通過class類的public Type getGenericSuperclass()方法,獲取帶泛型信息的父類Type,也就是TypeToken<String>
總結(jié):Gson利用子類會(huì)保存父類class的泛型參數(shù)信息的特點(diǎn)。 通過匿名內(nèi)部類實(shí)現(xiàn)了泛型參數(shù)的傳遞。
參考文獻(xiàn):