Java 深入理解泛型(轉(zhuǎn))

ArrayList就是個(gè)泛型類(lèi)屏镊,我們通過(guò)設(shè)定不同的類(lèi)型渠欺,可以往集合里面存儲(chǔ)不同類(lèi)型的數(shù)據(jù)類(lèi)型(而且只能存儲(chǔ)設(shè)定的數(shù)據(jù)類(lèi)型高蜂,這是泛型的優(yōu)勢(shì)之一)≌嵛梗“泛型”簡(jiǎn)單的意思就是泛指的類(lèi)型(參數(shù)化類(lèi)型)忘渔。想象下這樣的場(chǎng)景:如果我們現(xiàn)在要寫(xiě)一個(gè)容器類(lèi)(支持?jǐn)?shù)據(jù)增刪查詢(xún)的),我們寫(xiě)了支持String類(lèi)型的缰儿,后面還需要寫(xiě)支持Integer類(lèi)型的畦粮。然后呢?Doubel乖阵、Float宣赔、各種自定義類(lèi)型?這樣重復(fù)代碼太多了瞪浸,而且這些容器的算法都是一致的儒将。我們可以通過(guò)泛指一種類(lèi)型T,來(lái)代替我們之前需要的所有類(lèi)型,把我們需要的類(lèi)型作為參數(shù)傳遞到容器里面对蒲,這樣我們算法只需要寫(xiě)一套就可以適應(yīng)所有的類(lèi)型钩蚊。最典型的的例子就是ArrayList了,這個(gè)集合我們無(wú)論傳遞什么數(shù)據(jù)類(lèi)型蹈矮,它都能很好的工作砰逻。

聰明的同學(xué)看完上面的描述,靈機(jī)一動(dòng)泛鸟,寫(xiě)出了下面的代碼:

class MyList{
    private Object[] elements=new Object[10];
    private int size;
    
    public void add(Object item) {
        elements[size++]=item;
    }
    
    public Object get(int index) {
        return elements[index];
    }
}

這個(gè)代碼靈活性很高蝠咆,所有的類(lèi)型都可以向上轉(zhuǎn)型為Object類(lèi),這樣我們就可以往里面存儲(chǔ)各種類(lèi)型的數(shù)據(jù)了谈况。的確Java在泛型出現(xiàn)之前勺美,也是這么做的递胧。但是這樣的有一個(gè)問(wèn)題:如果集合里面數(shù)據(jù)很多,某一個(gè)數(shù)據(jù)轉(zhuǎn)型出現(xiàn)錯(cuò)誤赡茸,在編譯期是無(wú)法發(fā)現(xiàn)的缎脾。但是在運(yùn)行期會(huì)發(fā)生java.lang.ClassCastException。例如:

MyList myList=new MyList();
myList.add("A");
myList.add(1);
System.out.println(myList.get(0));
System.out.println((String)myList.get(1));

我們?cè)谶@個(gè)集合里面存儲(chǔ)了多個(gè)類(lèi)型(某些情況下容器可能會(huì)存儲(chǔ)多種類(lèi)型的數(shù)據(jù))占卧,如果數(shù)據(jù)量較多遗菠,轉(zhuǎn)型的時(shí)候難免會(huì)出現(xiàn)異常,而這些都是無(wú)法在編譯期得知的华蜒。而泛型一方面讓我們只能往集合中添加一種類(lèi)型的數(shù)據(jù)辙纬,同時(shí)可以讓我們?cè)诰幾g期就發(fā)現(xiàn)這些錯(cuò)誤,避免運(yùn)行時(shí)異常的發(fā)生叭喜,提升代碼的健壯性贺拣。

Java泛型介紹

下面我們來(lái)介紹Java泛型的相關(guān)內(nèi)容,下面會(huì)介紹以下幾個(gè)方面:

Java泛型類(lèi)
Java泛型方法
Java泛型接口
Java泛型擦除及其相關(guān)內(nèi)容
Java泛型通配符

Java泛型類(lèi)

類(lèi)結(jié)構(gòu)是面向?qū)ο笾凶罨镜脑匚嬖蹋绻覀兊念?lèi)需要有很好的擴(kuò)展性譬涡,那么我們可以將其設(shè)置成泛型的。假設(shè)我們需要一個(gè)數(shù)據(jù)的包裝類(lèi)啥辨,通過(guò)傳入不同類(lèi)型的數(shù)據(jù)涡匀,可以存儲(chǔ)相應(yīng)類(lèi)型的數(shù)據(jù)。我們看看這個(gè)簡(jiǎn)單的泛型類(lèi)的設(shè)計(jì):

class DataHolder<T>{
    T item;
    
    public void setData(T t) {
        this.item=t;
    }
    
    public T getData() {
        return this.item;
    }
}

泛型類(lèi)定義時(shí)只需要在類(lèi)名后面加上類(lèi)型參數(shù)即可溉知,當(dāng)然你也可以添加多個(gè)參數(shù)陨瘩,類(lèi)似于<K,V>,<T,E,K>等。這樣我們就可以在類(lèi)里面使用定義的類(lèi)型參數(shù)级乍。

泛型類(lèi)最常用的使用場(chǎng)景就是“元組”的使用舌劳。我們知道方法return返回值只能返回單個(gè)對(duì)象。如果我們定義一個(gè)泛型類(lèi)卡者,定義2個(gè)甚至3個(gè)類(lèi)型參數(shù)蒿囤,這樣我們r(jià)eturn對(duì)象的時(shí)候,構(gòu)建這樣一個(gè)“元組”數(shù)據(jù)崇决,通過(guò)泛型傳入多個(gè)對(duì)象材诽,這樣我們就可以一次性方法多個(gè)數(shù)據(jù)了。

Java泛型方法

前面我們介紹的泛型是作用于整個(gè)類(lèi)的恒傻,現(xiàn)在我們來(lái)介紹泛型方法脸侥。泛型方法既可以存在于泛型類(lèi)中,也可以存在于普通的類(lèi)中盈厘。如果使用泛型方法可以解決問(wèn)題睁枕,那么應(yīng)該盡量使用泛型方法。下面我們通過(guò)例子來(lái)看一下泛型方法的使用:

class DataHolder<T>{
    T item;
    
    public void setData(T t) {
        this.item=t;
    }
    
    public T getData() {
        return this.item;
    }
    
    /**
     * 泛型方法
     * @param e
     */
    public <E> void PrinterInfo(E e) {
        System.out.println(e);
    }
}

從上面的例子中,我們看到我們是在一個(gè)泛型類(lèi)里面定義了一個(gè)泛型方法printerInfo外遇。通過(guò)傳入不同的數(shù)據(jù)類(lèi)型注簿,我們都可以打印出來(lái)。在這個(gè)方法里面跳仿,我們定義了類(lèi)型參數(shù)E诡渴。這個(gè)E和泛型類(lèi)里面的T兩者之間是沒(méi)有關(guān)系的。哪怕我們將泛型方法設(shè)置成這樣:

//注意這個(gè)T是一種全新的類(lèi)型菲语,可以與泛型類(lèi)中聲明的T不是同一種類(lèi)型妄辩。
public <T> void PrinterInfo(T e) {
    System.out.println(e);
}
//調(diào)用方法
DataHolder<String> dataHolder=new DataHolder<>();
dataHolder.PrinterInfo(1);
dataHolder.PrinterInfo("AAAAA");
dataHolder.PrinterInfo(8.88f);

我們來(lái)看運(yùn)行結(jié)果:

1
AAAAA
8.88

這個(gè)泛型方法依然可以傳入Double、Float等類(lèi)型的數(shù)據(jù)山上。泛型方法里面的類(lèi)型參數(shù)T和泛型類(lèi)里面的類(lèi)型參數(shù)是不一樣的類(lèi)型眼耀,從上面的調(diào)用方式,我們也可以看出佩憾,泛型方法printInfo不受我們DataHolder中泛型類(lèi)型參數(shù)是String的影響哮伟。 我們來(lái)總結(jié)下泛型方法的幾個(gè)基本特征:

1.public與返回值中間非常重要,可以理解為聲明此方法為泛型方法鸯屿。

2.只有聲明了的方法才是泛型方法澈吨,泛型類(lèi)中的使用了泛型的成員方法并不是泛型方法把敢。

3.表明該方法將使用泛型類(lèi)型T寄摆,此時(shí)才可以在方法中使用泛型類(lèi)型T。

4.與泛型類(lèi)的定義一樣修赞,此處T可以隨便寫(xiě)為任意標(biāo)識(shí)婶恼,常見(jiàn)的如T、E柏副、K勾邦、V等形式的參數(shù)常用于表示泛型。

Java泛型接口

Java泛型接口的定義和Java泛型類(lèi)基本相同割择,下面是一個(gè)例子:

//定義一個(gè)泛型接口
public interface Generator<T> {
    public T next();
}

此處有兩點(diǎn)需要注意:

泛型接口未傳入泛型實(shí)參時(shí)眷篇,與泛型類(lèi)的定義相同,在聲明類(lèi)的時(shí)候荔泳,需將泛型的聲明也一起加到類(lèi)中蕉饼。例子如下:

/* 即:class DataHolder implements Generator<T>{
 * 如果不聲明泛型,如:class DataHolder implements Generator<T>玛歌,編譯器會(huì)報(bào)錯(cuò):"Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}

如果泛型接口傳入類(lèi)型參數(shù)時(shí)昧港,實(shí)現(xiàn)該泛型接口的實(shí)現(xiàn)類(lèi),則所有使用泛型的地方都要替換成傳入的實(shí)參類(lèi)型支子。例子如下:

class DataHolder implements Generator<String>{
    @Override
    public String next() {
        return null;
    }
}

從這個(gè)例子我們看到创肥,實(shí)現(xiàn)類(lèi)里面的所有T的地方都需要實(shí)現(xiàn)為String。

Java泛型擦除及其相關(guān)內(nèi)容

我們下面看一個(gè)例子:

Class<?> class1=new ArrayList<String>().getClass();
Class<?> class2=new ArrayList<Integer>().getClass();
System.out.println(class1);     //class java.util.ArrayList
System.out.println(class2);     //class java.util.ArrayList
System.out.println(class1.equals(class2));  //true

我們看輸出發(fā)現(xiàn)叹侄,class1和class2居然是同一個(gè)類(lèi)型ArrayList趾代,在運(yùn)行時(shí)我們傳入的類(lèi)型變量String和Integer都被丟掉了稽坤。Java語(yǔ)言泛型在設(shè)計(jì)的時(shí)候?yàn)榱思嫒菰瓉?lái)的舊代碼尿褪,Java的泛型機(jī)制使用了“擦除”機(jī)制杖玲。我們來(lái)看一個(gè)更徹底的例子:

class Table {}
class Room {}
class House<Q> {}
class Particle<POSITION, MOMENTUM> {}
//調(diào)用代碼及輸出
List<Table> tableList = new ArrayList<Table>();
Map<Room, Table> maps = new HashMap<Room, Table>();
House<Room> house = new House<Room>();
Particle<Long, Double> particle = new Particle<Long, Double>();
System.out.println(Arrays.toString(tableList.getClass().getTypeParameters()));
System.out.println(Arrays.toString(maps.getClass().getTypeParameters()));
System.out.println(Arrays.toString(house.getClass().getTypeParameters()));
System.out.println(Arrays.toString(particle.getClass().getTypeParameters()));
/** 
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]
 */

上面的代碼里臼闻,我們想在運(yùn)行時(shí)獲取類(lèi)的類(lèi)型參數(shù)述呐,但是我們看到返回的都是“形參”乓搬。在運(yùn)行期我們是獲取不到任何已經(jīng)聲明的類(lèi)型信息的进肯。

注意:

編譯器雖然會(huì)在編譯過(guò)程中移除參數(shù)的類(lèi)型信息棉磨,但是會(huì)保證類(lèi)或方法內(nèi)部參數(shù)類(lèi)型的一致性乘瓤。

泛型參數(shù)將會(huì)被擦除到它的第一個(gè)邊界(邊界可以有多個(gè),重用 extends 關(guān)鍵字斟赚,通過(guò)它能給與參數(shù)類(lèi)型添加一個(gè)邊界)拗军。編譯器事實(shí)上會(huì)把類(lèi)型參數(shù)替換為它的第一個(gè)邊界的類(lèi)型交掏。如果沒(méi)有指明邊界盅弛,那么類(lèi)型參數(shù)將被擦除到Object挪鹏。下面的例子中讨盒,可以把泛型參數(shù)T當(dāng)作HasF類(lèi)型來(lái)使用返顺。

public interface HasF {
    void f();
}

public class Manipulator<T extends HasF> {
    T obj;
    public T getObj() {
        return obj;
    }
    public void setObj(T obj) {
        this.obj = obj;
    }
}

extend關(guān)鍵字后后面的類(lèi)型信息決定了泛型參數(shù)能保留的信息遂鹊。Java類(lèi)型擦除只會(huì)擦除到HasF類(lèi)型秉扑。

Java泛型擦除的原理

我們通過(guò)例子來(lái)看一下邻储,先看一個(gè)非泛型的版本:

// SimpleHolder.java
public class SimpleHolder {
    private Object obj;
    public Object getObj() {
        return obj;
    }
    public void setObj(Object obj) {
        this.obj = obj;
    }
    public static void main(String[] args) {
        SimpleHolder holder = new SimpleHolder();
        holder.setObj("Item");
        String s = (String) holder.getObj();
    }
}
// SimpleHolder.class
public class SimpleHolder {
  public SimpleHolder();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public java.lang.Object getObj();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field obj:Ljava/lang/Object;
       4: areturn       

  public void setObj(java.lang.Object);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #2                  // Field obj:Ljava/lang/Object;
       5: return        

  public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class SimpleHolder
       3: dup           
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1      
       8: aload_1       
       9: ldc           #5                  // String Item
      11: invokevirtual #6                  // Method setObj:(Ljava/lang/Object;)V
      14: aload_1       
      15: invokevirtual #7                  // Method getObj:()Ljava/lang/Object;
      18: checkcast     #8                  // class java/lang/String
      21: astore_2      
      22: return        
}

下面我們給出一個(gè)泛型的版本旧噪,從字節(jié)碼的角度來(lái)看看:

//GenericHolder.java
public class GenericHolder<T> {
    T obj;
    public T getObj() {
        return obj;
    }
    public void setObj(T obj) {
        this.obj = obj;
    }
    public static void main(String[] args) {
        GenericHolder<String> holder = new GenericHolder<>();
        holder.setObj("Item");
        String s = holder.getObj();
    }
}

//GenericHolder.class
public class GenericHolder<T> {
  T obj;

  public GenericHolder();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public T getObj();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field obj:Ljava/lang/Object;
       4: areturn       

  public void setObj(T);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #2                  // Field obj:Ljava/lang/Object;
       5: return        

  public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class GenericHolder
       3: dup           
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1      
       8: aload_1       
       9: ldc           #5                  // String Item
      11: invokevirtual #6                  // Method setObj:(Ljava/lang/Object;)V
      14: aload_1       
      15: invokevirtual #7                  // Method getObj:()Ljava/lang/Object;
      18: checkcast     #8                  // class java/lang/String
      21: astore_2      
      22: return        
}

在編譯過(guò)程中脓匿,類(lèi)型變量的信息是能拿到的。所以陪毡,set方法在編譯器可以做類(lèi)型檢查米母,非法類(lèi)型不能通過(guò)編譯。但是對(duì)于get方法毡琉,由于擦除機(jī)制,運(yùn)行時(shí)的實(shí)際引用類(lèi)型為Object類(lèi)型身辨。為了“還原”返回結(jié)果的類(lèi)型芍碧,編譯器在get之后添加了類(lèi)型轉(zhuǎn)換泌豆。所以踪危,在GenericHolder.class文件main方法主體第18行有一處類(lèi)型轉(zhuǎn)換的邏輯。它是編譯器自動(dòng)幫我們加進(jìn)去的贞远。

所以在泛型類(lèi)對(duì)象讀取和寫(xiě)入的位置為我們做了處理敛滋,為代碼添加約束。

Java泛型擦除的缺陷及補(bǔ)救措施

泛型類(lèi)型不能顯式地運(yùn)用在運(yùn)行時(shí)類(lèi)型的操作當(dāng)中兴革,例如:轉(zhuǎn)型绎晃、instanceof 和 new。因?yàn)樵谶\(yùn)行時(shí)杂曲,所有參數(shù)的類(lèi)型信息都丟失了庶艾。類(lèi)似下面的代碼都是無(wú)法通過(guò)編譯的:

public class Erased<T> {
    private final int SIZE = 100;
    public static void f(Object arg) {
        //編譯不通過(guò)
        if (arg instanceof T) {
        }
        //編譯不通過(guò)
        T var = new T();
        //編譯不通過(guò)
        T[] array = new T[SIZE];
        //編譯不通過(guò)
        T[] array = (T) new Object[SIZE];
    }
}

那我們有什么辦法來(lái)補(bǔ)救呢?下面介紹幾種方法來(lái)一一解決上面出現(xiàn)的問(wèn)題擎勘。

類(lèi)型判斷問(wèn)題

我們可以通過(guò)下面的代碼來(lái)解決泛型的類(lèi)型信息由于擦除無(wú)法進(jìn)行類(lèi)型判斷的問(wèn)題:

/**
 * 泛型類(lèi)型判斷封裝類(lèi)
 * @param <T>
 */
class GenericType<T>{
    Class<?> classType;
    
    public GenericType(Class<?> type) {
        classType=type;
    }
    
    public boolean isInstance(Object object) {
        return classType.isInstance(object);
    }
}

在main方法我們可以這樣調(diào)用:

GenericType<A> genericType=new GenericType<>(A.class);
System.out.println("------------");
System.out.println(genericType.isInstance(new A()));
System.out.println(genericType.isInstance(new B()));

我們通過(guò)記錄類(lèi)型參數(shù)的Class對(duì)象咱揍,然后通過(guò)這個(gè)Class對(duì)象進(jìn)行類(lèi)型判斷。

創(chuàng)建類(lèi)型實(shí)例

泛型代碼中不能new T()的原因有兩個(gè)棚饵,一是因?yàn)椴脸喝梗荒艽_定類(lèi)型;而是無(wú)法確定T是否包含無(wú)參構(gòu)造函數(shù)噪漾。

為了避免這兩個(gè)問(wèn)題硼砰,我們使用顯式的工廠模式:

/**
 * 使用工廠方法來(lái)創(chuàng)建實(shí)例
 *
 * @param <T>
 */
interface Factory<T>{
    T create();
}

class Creater<T>{
    T instance;
    public <F extends Factory<T>> T newInstance(F f) {
        instance=f.create();
        return instance;
    }
}

class IntegerFactory implements Factory<Integer>{
    @Override
    public Integer create() {
        Integer integer=new Integer(9);
        return integer;
    }
}

我們通過(guò)工廠模式+泛型方法來(lái)創(chuàng)建實(shí)例對(duì)象,上面代碼中我們創(chuàng)建了一個(gè)IntegerFactory工廠欣硼,用來(lái)創(chuàng)建Integer實(shí)例题翰,以后代碼有變動(dòng)的話(huà),我們可以添加新的工廠類(lèi)型即可诈胜。

調(diào)用代碼如下:

Creater<Integer> creater=new Creater<>();
System.out.println(creater.newInstance(new IntegerFactory()));

創(chuàng)建泛型數(shù)組

一般不建議創(chuàng)建泛型數(shù)組豹障。盡量使用ArrayList來(lái)代替泛型數(shù)組。但是在這里還是給出一種創(chuàng)建泛型數(shù)組的方法

public class GenericArrayWithTypeToken<T> {
    private T[] array;

    @SuppressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type, int sz) {
        array = (T[]) Array.newInstance(type, sz);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T[] rep() {
        return array;
    }

    public static void main(String[] args) {
        
    }
}

這里我們使用的還是傳參數(shù)類(lèi)型焦匈,利用類(lèi)型的newInstance方法創(chuàng)建實(shí)例的方式血公。

Java泛型的通配符

上界通配符<? extends T>

我們先來(lái)看一個(gè)例子:

class Fruit {}
class Apple extends Fruit {}

現(xiàn)在我們定義一個(gè)盤(pán)子類(lèi):

class Plate<T>{
    T item;
    public Plate(T t){
        item=t;
    }
    
    public void set(T t) {
        item=t;
    }
    
    public T get() {
        return item;
    }
}

下面,我們定義一個(gè)水果盤(pán)子缓熟,理論上水果盤(pán)子里累魔,當(dāng)然可以存在蘋(píng)果

Plate<Fruit> p=new Plate<Apple>(new Apple());

你會(huì)發(fā)現(xiàn)這段代碼無(wú)法進(jìn)行編譯摔笤。裝蘋(píng)果的盤(pán)子”無(wú)法轉(zhuǎn)換成“裝水果的盤(pán)子:

cannot convert from Plate<Apple> to Plate<Fruit>

從上面代碼我們知道,就算容器中的類(lèi)型之間存在繼承關(guān)系薛夜,但是Plate和Plate兩個(gè)容器之間是不存在繼承關(guān)系的籍茧。 在這種情況下,Java就設(shè)計(jì)成Plate<? extend Fruit>來(lái)讓兩個(gè)容器之間存在繼承關(guān)系梯澜。我們上面的代碼就可以進(jìn)行賦值了

Plate<? extends Fruit> p=new Plate<Apple>(new Apple());

Plate<? extend Fruit>是Plate< Fruit >和Plate< Apple >的基類(lèi)寞冯。

我們通過(guò)一個(gè)更加詳細(xì)的例子來(lái)看一下上界的界限:

class Food{}

class Fruit extends Food {}
class Meat extends Food {}

class Apple extends Fruit {}
class Banana extends Fruit {}
class Pork extends Meat{}
class Beef extends Meat{}

class RedApple extends Apple {}
class GreenApple extends Apple {}

在上面這個(gè)類(lèi)層次中,Plate<? extend Fruit>晚伙,覆蓋下面的藍(lán)色部分:

image

如果我們往盤(pán)子里面添加數(shù)據(jù)吮龄,例如:

p.set(new Fruit());
p.set(new Apple());

你會(huì)發(fā)現(xiàn)無(wú)法往里面設(shè)置數(shù)據(jù),按道理說(shuō)我們將泛型類(lèi)型設(shè)置為? extend Fruit咆疗。按理說(shuō)我們往里面添加Fruit的子類(lèi)應(yīng)該是可以的漓帚。但是Java編譯器不允許這樣操作。<? extends Fruit>會(huì)使往盤(pán)子里放東西的set()方法失效午磁。但取東西get()方法還有效

原因是:

Java編譯期只知道容器里面存放的是Fruit和它的派生類(lèi)尝抖,具體是什么類(lèi)型不知道,可能是Fruit迅皇?可能是Apple昧辽?也可能是Banana登颓,RedApple搅荞,GreenApple?編譯器在后面看到Plate< Apple >賦值以后框咙,盤(pán)子里面沒(méi)有標(biāo)記為“蘋(píng)果”咕痛。只是標(biāo)記了一個(gè)占位符“CAP#1”,來(lái)表示捕獲一個(gè)Fruit或者Fruit的派生類(lèi)喇嘱,具體是什么類(lèi)型不知道茉贡。所有調(diào)用代碼無(wú)論往容器里面插入Apple或者M(jìn)eat或者Fruit編譯器都不知道能不能和這個(gè)“CAP#1”匹配,所以這些操作都不允許婉称。

但是上界通配符是允許讀取操作的块仆。例如代碼:

Fruit fruit=p.get();
Object object=p.get();

這個(gè)我們很好理解,由于上界通配符設(shè)定容器中只能存放Fruit及其派生類(lèi)王暗,那么獲取出來(lái)的我們都可以隱式的轉(zhuǎn)為其基類(lèi)(或者Object基類(lèi))。所以上界描述符Extends適合頻繁讀取的場(chǎng)景庄敛。

下界通配符<? super T>

下界通配符的意思是容器中只能存放T及其T的基類(lèi)類(lèi)型的數(shù)據(jù)俗壹。我們還是以上面類(lèi)層次的來(lái)看,<? super Fruit>覆蓋下面的紅色部分:

image

下界通配符<? super T>不影響往里面存儲(chǔ)藻烤,但是讀取出來(lái)的數(shù)據(jù)只能是Object類(lèi)型绷雏。

原因是:

下界通配符規(guī)定了元素最小的粒度头滔,必須是T及其基類(lèi),那么我往里面存儲(chǔ)T及其派生類(lèi)都是可以的涎显,因?yàn)樗伎梢噪[式的轉(zhuǎn)化為T(mén)類(lèi)型坤检。但是往外讀就不好控制了,里面存儲(chǔ)的都是T及其基類(lèi)期吓,無(wú)法轉(zhuǎn)型為任何一種類(lèi)型早歇,只有Object基類(lèi)才能裝下。

PECS原則

最后簡(jiǎn)單介紹下Effective Java這本書(shū)里面介紹的PECS原則讨勤。

上界<? extends T>不能往里存箭跳,只能往外取,適合頻繁往外面讀取內(nèi)容的場(chǎng)景潭千。

下界<? super T>不影響往里存谱姓,但往外取只能放在Object對(duì)象里,適合經(jīng)常往里面插入數(shù)據(jù)的場(chǎng)景刨晴。

<?>無(wú)限通配符

無(wú)界通配符 意味著可以使用任何對(duì)象屉来,因此使用它類(lèi)似于使用原生類(lèi)型。但它是有作用的狈癞,原生類(lèi)型可以持有任何類(lèi)型茄靠,而無(wú)界通配符修飾的容器持有的是某種具體的類(lèi)型。舉個(gè)例子亿驾,在List類(lèi)型的引用中嘹黔,不能向其中添加Object, 而List類(lèi)型的引用就可以添加Object類(lèi)型的變量。

最后提醒一下的就是莫瞬,List與List并不等同儡蔓,List是List的子類(lèi)。還有不能往List<?> list里添加任意對(duì)象疼邀,除了null喂江。

作者:dreamGong,著作權(quán)歸作者所有

鏈接:https://juejin.im/post/5b614848e51d45355d51f792

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旁振,一起剝皮案震驚了整個(gè)濱河市获询,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拐袜,老刑警劉巖吉嚣,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蹬铺,居然都是意外死亡尝哆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)甜攀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秋泄,“玉大人琐馆,你說(shuō)我怎么就攤上這事『阈颍” “怎么了瘦麸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)歧胁。 經(jīng)常有香客問(wèn)我滋饲,道長(zhǎng),這世上最難降的妖魔是什么与帆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任了赌,我火速辦了婚禮,結(jié)果婚禮上玄糟,老公的妹妹穿的比我還像新娘勿她。我一直安慰自己,他們只是感情好阵翎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布逢并。 她就那樣靜靜地躺著,像睡著了一般郭卫。 火紅的嫁衣襯著肌膚如雪砍聊。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天贰军,我揣著相機(jī)與錄音玻蝌,去河邊找鬼。 笑死词疼,一個(gè)胖子當(dāng)著我的面吹牛俯树,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贰盗,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼许饿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了舵盈?” 一聲冷哼從身側(cè)響起陋率,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秽晚,沒(méi)想到半個(gè)月后瓦糟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赴蝇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年狸页,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扯再。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芍耘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熄阻,到底是詐尸還是另有隱情斋竞,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布秃殉,位于F島的核電站坝初,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钾军。R本人自食惡果不足惜鳄袍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吏恭。 院中可真熱鬧拗小,春花似錦、人聲如沸樱哼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)搅幅。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谋作,已是汗流浹背舀锨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沪编,地道東北人呼盆。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像漾抬,于是被迫代替她去往敵國(guó)和親宿亡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351