泛型相關(guān)筆記

泛型的目的

在編譯階段完成類型的轉(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):

Java Type 類型詳解

java 的泛型擦除與 TypeToken

【知識(shí)點(diǎn)】Java泛型機(jī)制7連問

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java編程思想---泛型(2) 類型擦除 先上例子: ArrayList< String >與ArrayList...
    Cool_Pomelo閱讀 336評(píng)論 0 0
  • JAVA-泛型 sschrodinger 2018/11/15 簡介 泛型是Java SE 1.5的新特性,泛型的...
    sschrodinger閱讀 593評(píng)論 0 2
  • [TOC] 深入理解 Java 泛型 概述 泛型的本質(zhì)是參數(shù)化類型,通常用于輸入?yún)?shù)、存儲(chǔ)類型不確定的場景。相比于...
    albon閱讀 5,826評(píng)論 0 7
  • 最近終于開始總結(jié)自己理解的東西,終于有時(shí)間寫寫自己對(duì)一些知識(shí)的認(rèn)識(shí)。文筆不好,還望各位包含。以下知識(shí)點(diǎn)是我看了享學(xué)...
    Jack_Ou閱讀 825評(píng)論 0 2
  • http://www.importnew.com/24029.html 泛型基礎(chǔ) 泛型類 這樣做的壞處是 Box ...
    ColdWave閱讀 364評(píng)論 0 0

友情鏈接更多精彩內(nèi)容