《Java架構築基》從Java基礎講起——泛型的使用

一. 泛型的使用

1. 泛型類的概述及使用

  • A:泛型類概述: 把泛型定義在類上
  • B:定義格式: public class 類名<泛型類型1,…>
  • C:注意事項: 泛型類型必須是引用類型

2. 泛型方法的概述和使用

  • A:泛型方法概述: 把泛型定義在方法上
  • B:定義格式: public <泛型類型> 返回類型 方法名(泛型類型 變量名)
public <T> void show(T t) {

}

所謂泛型方法,就是在聲明方法時定義一個或多個類型形參。 泛型方法的用法格式如下:

修飾符<T, S> 返回值類型 方法名(形參列表){
   方法體
}

注意要點

  • 方法聲明中定義的形參只能在該方法裏使用,而接口、類聲明中定義的類型形參則可以在整個接口、類中使用。當調用fun()方法時,根據傳入的實際對象,編譯器就會判斷出類型形參T所代表的實際類型。
class Demo{  
  public <T> T fun(T t){   // 可以接收任意類型的數據  
   return t ;     // 直接把參數返回  
  }  
};  
public class GenericsDemo26{  
  public static void main(String args[]){  
    Demo d = new Demo() ; // 實例化Demo對象  
    String str = d.fun("湯姆") ; // 傳遞字符串  
    int i = d.fun(30) ;  // 傳遞數字,自動裝箱  
    System.out.println(str) ; // 輸出內容  
    System.out.println(i) ;  // 輸出內容  
  }  
};

3. 泛型接口的概述和使用

先來看一個案例

  • A:泛型接口概述: 把泛型定義在接口上
  • B:定義格式: public interface 接口名<泛型類型>
/**
 * 泛型接口的定義格式:        修飾符  interface 接口名<數據類型> {}
 */
public interface Inter<T> {
    public abstract void show(T t) ;
}

/**
 * 子類是泛型類
 */
public class InterImpl<E> implements Inter<E> {
    @Override
    public void show(E t) {
        System.out.println(t);
    }
}

Inter<String> inter = new InterImpl<String>() ;
inter.show("hello") ;

然後看看源碼中泛型的使用,下面是JDK 1.5 以後,List接口,以及ArrayList類的代碼片段。

//定義接口時指定了一個類型形參,該形參名爲E
public interface List<E> extends Collection<E> {
   //在該接口裏,E可以作爲類型使用
   public E get(int index) {}
   public void add(E e) {} 
}

//定義類時指定了一個類型形參,該形參名爲E
public class ArrayList<E> extends AbstractList<E> implements List<E> {
   //在該類裏,E可以作爲類型使用
   public void set(E e) {
   .......................
   }
}

這就是泛型的實質:允許在定義接口、類時聲明類型形參,類型形參在整個接口、類體內可當成類型使用,幾乎所有可使用普通類型的地方都可以使用這種類型形參。

泛型類派生子類

  • 當創建了帶泛型聲明的接口、父類之後,可以爲該接口創建實現類,或者從該父類派生子類,需要注意:使用這些接口、父類派生子類時不能再包含類型形參,需要傳入具體的類型。
  • 錯誤的方式:
public class A extends Container<K, V>{}
  • 正確的方式:
public class A extends Container<Integer, String>{}
  • 也可以不指定具體的類型,此時系統會把K,V形參當成Object類型處理。如下:
public class A extends Container{}

4. 泛型類的概述和使用

定義一個容器類,存放鍵值對key-value,鍵值對的類型不確定,可以使用泛型來定義,分別指定爲K和V。

public class Container<K, V> {

    private K key;
    private V value;

    public Container(K k, V v) {
        key = k;
        value = v;
    }

    public K getkey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    public void setKey() {
        this.key = key;
    }

    public void setValue() {
        this.value = value;
    }

}

在使用Container類時,只需要指定K,V的具體類型即可,從而創建出邏輯上不同的Container實例,用來存放不同的數據類型。

public static void main(String[] args) {
    Container<String,String>  c1=new Container<String ,String>("name","hello");
    Container<String,Integer> c2=new Container<String,Integer>("age",22);
    Container<Double,Double>  c3=new Container<Double,Double>(1.1,1.3);
    System.out.println(c1.getKey() + " : " + c1.getValue());      
    System.out.println(c2.getKey() + " : " + c2.getValue());                                                               
    System.out.println(c3.getKey() + " : " + c3.getValue());
}

在JDK 1.7 增加了泛型的“菱形”語法:Java允許在構造器後不需要帶完成的泛型信息,只要給出一對尖括號(<>)即可,Java可以推斷尖括號裏應該是什麼泛型信息。 如下所示:

Container<String,String> c1=new Container<>("name","hello");
Container<String,Integer> c2=new Container<>("age",22);

5. 泛型構造器的概述

  • 正如泛型方法允許在方法簽名中聲明類型形參一樣,Java也允許在構造器簽名中聲明類型形參,這樣就產生了所謂的泛型構造器。
  • 和使用普通泛型方法一樣沒區別,一種是顯式指定泛型參數,另一種是隱式推斷,如果是顯式指定則以顯式指定的類型參數爲準,如果傳入的參數的類型和指定的類型實參不符,將會編譯報錯。
    public class Person {
    public <T> Person(T t) {
        System.out.println(t);
    }
    }
  • 如何使用
    public static void main(String[] args){
    //隱式
    new Person(22);
    //顯示
    new<String> Person("hello");
    }

這裏唯一需要特殊註明的就是:

如果構造器是泛型構造器,同時該類也是一個泛型類的情況下應該如何使用泛型構造器:因爲泛型構造器可以顯式指定自己的類型參數(需要用到菱形,放在構造器之前),而泛型類自己的類型實參也需要指定(菱形放在構造器之後),這就同時出現了兩個菱形了,這就會有一些小問題,具體用法再這裏總結一下。

以下面這個例子爲代表

public class Person<E> {
    public <T> Person(T t) {
        System.out.println(t);
    }
}

這種用法:Person<String> a = new <Integer>Person<>(15);這種語法不允許,會直接編譯報錯!

二. 泛型高級之通配符

1. 爲什麼要使用通配符

通配符的設計存在一定的場景,例如在使用泛型後,首先聲明瞭一個Animal的類,而後聲明瞭一個繼承Animal類的Cat類,顯然Cat類是Animal類的子類,但是List卻不是List的子類型,而在程序中往往需要表達這樣的邏輯關係。爲了解決這種類似的場景,在泛型的參數類型的基礎上新增了通配符的用法。

2. <? extends T> 上界通配符

上界通配符顧名思義,<? extends T>表示的是類型的上界【包含自身】,因此通配的參數化類型可能是T或T的子類。

  • 正因爲無法確定具體的類型是什麼,add方法受限(可以添加null,因爲null表示任何類型),但可以從列表中獲取元素後賦值給父類型。如上圖中的第一個例子,第三個add()操作會受限,原因在於List和List是List<? extends Animal>的子類型。
它表示集合中的所有元素都是Animal類型或者其子類
List<? extends Animal>

這就是所謂的上限通配符,使用關鍵字extends來實現,實例化時,指定類型實參只能是extends後類型的子類或其本身。

  • 例如:
  • 這樣就確定集合中元素的類型,雖然不確定具體的類型,但最起碼知道其父類。然後進行其他操作。
//Cat是其子類
List<? extends Animal> list = new ArrayList<Cat>();

3. <? super T> 下界通配符

下界通配符<? super T>表示的是參數化類型是T的超類型(包含自身),層層至上,直至Object

  • 編譯器無從判斷get()返回的對象的類型是什麼,因此get()方法受限。但是可以進行add()方法,add()方法可以添加T類型和T類型的子類型,如第二個例子中首先添加了一個Cat類型對象,然後添加了兩個Cat子類類型的對象,這種方法是可行的,但是如果添加一個Animal類型的對象,顯然將繼承的關係弄反了,是不可行的。
它表示集合中的所有元素都是Cat類型或者其父類
List <? super Cat>

這就是所謂的下限通配符,使用關鍵字super來實現,實例化時,指定類型實參只能是extends後類型的子類或其本身。

例如:

//Shape是其父類
List<? super Cat> list = new ArrayList<Animal>();

4. <?> ***通配符

  • 任意類型,如果沒有明確,那麼就是Object以及任意的Java類了
  • ***通配符用<?>表示,?代表了任何的一種類型,能代表任何一種類型的只有null(Object本身也算是一種類型,但卻不能代表任何一種類型,所以List和List的含義是不同的,前者類型是Object,也就是繼承樹的最上層,而後者的類型完全是未知的)。

三. 泛型只能使用引用類型

當聲明泛型類的實例時,傳遞的類型參數必須是引用類型,不能使用基本類型

  • 例如,對於 User 泛型類來說,以下聲明是非法的
    User<int,double> user=new User<>(1,10,0);

如何解決

  • 可以使用類型的包裝類來解決該問題
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章