Java 中的泛型

來源:互聯網
上載者:User

先來看一下以下 2 段代碼,然後再進一步引出我們的泛型。

 

  public static void main(String[] args) {        List list = new ArrayList();        list.add("123");        list.add(456);        Iterator it = list.iterator();        while(it.hasNext()){            // Error : Integer cannot be cast to String            String next = (String)it.next();          }    }

 

上面這段代碼,會出現轉化異常的情況,但是編譯是沒問題的,在輸出轉化的時候卻出現了異常,有沒有一種衝動想要把集合中的類型歸一?下面就是很正常的一個求和的方法,然而我們只能求類型為 Integer 的參數的和。

 

public Integer add(Integer a,Integer b){    return a + b;}

 

對於集合來說,我們若是能在編譯時間期指定該集合中存放資料的類型,這樣在類型轉化的時候就不會再出現錯誤了,同樣的,在下面的求和方法中,這個方法我們只能求得類型為 Integer 的參數的和,我們能不能做到可以通用的求和呢?使用泛型,就可以做到。

 

泛型的概念也就是 “ 資料類型參數化 ” 正是由於我們不確定集合中可以指定的類型有哪些,是 Integer 還是 String ?求和方法中參數的資料類型可以有哪些,是 Float 還是 Double ?那我們就可以使用泛型來把這個資料類型給參數化。

 

泛型的應用有泛型介面,泛型類和泛型方法。下面定義一個泛型類,並示範使用方式。

 

public class Box <T> {    // T 是 Type 的簡寫,代表任意類型,注意是類,而不是基礎資料型別 (Elementary Data Type)。    // 也可以換成其它單詞,這隻是一個表示而已。    T t;    public T getT() {        return t;    }    public void setT(T t) {        this.t = t;    }    // 在下面的應用中,我們可以將 T 換成任意我們想要的類型    public static void main(String[] args) {        Box<Integer> iBox = new Box<Integer>();        Box<Double> dBox = new Box<Double>();        // 在 JDK1.7 及其以上版本可以利用 “類型推斷” 這樣寫。        Box<String> stringBox = new Box<>();    }}

 

泛型方法的定義只需要在方法的聲明中添加 < T > 即可,或是添加一組泛型 <K ,V> 。 

 

public class Util {    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {        return p1.getKey().equals(p2.getKey()) &&               p1.getValue().equals(p2.getValue());    }}public class Pair<K, V> {    private K key;    private V value;    public Pair(K key, V value) {        this.key = key;        this.value = value;    }    public void setKey(K key) { this.key = key; }    public void setValue(V value) { this.value = value; }    public K getKey()   { return key; }    public V getValue() { return value; }}

 

我們可以這樣來調用泛型方法。

 

Pair<Integer, String> p1 = new Pair<>(1, "apple");Pair<Integer, String> p2 = new Pair<>(2, "pear");boolean same = Util.<Integer, String>compare(p1, p2);

 

以上也就是簡單的應用,那我們還會遇到什麼情況呢,下面看一看關於萬用字元的問題,為了示範效果,我寫了一個實際用處不大的方法。

     

public static void printSize(List<Object> list){        System.out.println(list.size());    }    public static void main(String[] args) {        List<Integer> list1 = new ArrayList<>();        List<String> list2 = new ArrayList<>();        printSize(list1); // 報錯    }

 

上面我們已經知道,在集合中使用泛型可以使程式更加安全,易讀,所以 Java 規範推薦我們使用泛型,那我們看一下上面這種情況,我該如何表示接收所有帶有泛型的 List 集合呢。上面使用 LIst<Object> 是不行的。為什麼不行?我們該使用何種方式接收參數呢?

 

首先來解釋一下為什麼不行,先看一下簡單的區別。

 

Object obj = new Integer(1);ArrayList<Object> list = new ArrayList<Integer>(); // 報錯

 

obj 可以賦值成功,是因為多態(往深了說是裡氏替換原則),父類的引用指向了子類的實體,而下面的報錯了,直觀的理由說明,ArrayList<Object> 不是 ArrayList<Integer> 的父類。嗯,確實不是,為什麼不是,一句話帶過,是因為 Java 中正常的泛型是不變的,當然我們也可以使其改變。(不變,協變和逆變的概念可以自行百度)

傳送門:www.cnblogs.com/keyi/p/6068921.html

那我就是想讓方法接收所有的帶泛型的集合該怎麼辦呢?這時候萬用字元就出現了,我們可以使用 List<?> 代表所有的帶泛型的 List ,這樣也就是說可以使用 List<?> 來指向所有的帶泛型的 LIst 。

 

public static void printSize(List<?> list){        System.out.println(list.size());    }    public static void main(String[] args) {        List<Integer> list1 = new ArrayList<>();        List<String> list2 = new ArrayList<>();        printSize(list1);        printSize(list2);    }

 

好的,再次梳理一下邏輯,在 Java 中我們可以使用父類的引用指向子類的對象,而在泛型中,List<Object> 和 List<Integer> 不構成繼承關係,原因是因為泛型是不可變的,然而我們又希望表示所有帶有泛型的集合,這時就出現了 ?萬用字元。我們可以使用 List<?> 來引用其它帶泛型的 List 。

 

實際的效果就是這樣

 

List<Object> list1 = new ArrayList<Integer>(); // 報錯List<?> list2 = new ArrayList<Integer>();

 

那好,現在要求升級了,我希望我的 List 集合不要什麼都可以指向,下面就看一下一些有限制條件的修飾符該如何表示。

 

// 通用修飾符List<?> list1 = new ArrayList<Integer>();// <? extends T> 可用於表示 T 以及 T 的子類        List<? extends Number> list2 = new ArrayList<Number>();List<? extends Number> list3 = new ArrayList<Integer>();List<? extends Number> list4 = new ArrayList<String>(); // 報錯// <? super T> 可用於表示 T 以及 T 的父類        List<? super Number> list5 = new ArrayList<Number>();List<? super Number> list6 = new ArrayList<Object>();List<? super Number> list7 = new ArrayList<Integer>(); //報錯

 

對於上面的 <? extends Number> 和 <? super Number> 該如何選擇呢 ?先說結論:” Producer Extends,Consumer Super ” 簡稱 PECS 原則。

“Producer Extends” – 如果你需要一個唯讀 List,用它來 produce T,那麼使用< ? extends T > 。

“Consumer Super” – 如果你需要一個唯寫 List,用它來 consume T,那麼使用< ? super T > 。

如果需要同時讀取以及寫入,那麼我們就不能使用萬用字元了。

 

List<? extends Number> list = new ArrayList<Number>();List<? extends Number> list = new ArrayList<Integer>();List<? extends Number> list = new ArrayList<Double>();// 不論具體的執行個體化是什麼,我們 get 元素之後都是父類 Number// 所以是 produce ,可以 get 得到很多的 TNumber number = list.get(0);list.add(new Integer(1)); // 報錯

 

由於以上的三種執行個體化方式都是允許的,那麼假如我現在想從 list 中 get 一個執行個體,因為 list 指向的執行個體可能是 Animal ,Dog,或 Cat 執行個體的集合。所以返回的值會統一為其父類。而在 add 值的時候就會存在問題,我不能確定添加的元素具體是哪一個,除了 null ,所以會報錯。

 

同樣的思路再來看< ? super T > 操作。

 

List<? super Integer> list = new ArrayList<Integer>();List<? super Integer> list = new ArrayList<Number>();List<? super Integer> list = new ArrayList<Object>();// 不同的執行個體化,我們 get 元素之後返回的值不確定// 或是 Integer, Number, Object ……list.get(0); // 添加資料的時候可以確定的添加是什麼// 所以 super 對應唯寫入的情況,即 consume Tlist.add(new Integer(1)); 

 

關於泛型還要說明的是泛型是應用在編譯時間期的一項技術,而在運行期間是不存在泛型的。原因在於泛型型別擦除。為什麼這麼說,我們可以來看個樣本

 

public static void main(String[] args) {        List<Integer> list = new ArrayList<Integer>();        list.add(456);        list.add("123");// 編譯報錯    }    --------------------------------------------------    public static void main(String[] args) {          List<String> l1 = new ArrayList<String>();           List<Integer> l2 = new ArrayList<Integer>();        System.out.println(l1.getClass());        System.out.println(l1.getClass().equals(l2.getClass()));      }     // class java.util.ArrayList    // true 

 

究其原因,在於 Java 中的泛型這一概念提出的目的,導致其只是作用於代碼編譯階段,在編譯過程中,正確檢驗泛型結果後,會將泛型的相關資訊擦出,也就是說,成功編譯過後的 class 檔案中是不包含任何泛型資訊的。泛型資訊不會進入到運行時階段。

 

在類型擦除之後,若是在代碼中有相應的類型變數,遵循 " 保留上界 " 規則,會將相應的 T 替換成具體的類。

< ? > ---- > Object

< ? extends T > ---- > T

< ? super T > ----- > Object

補充說明一點,Java 中不允許直接建立泛型數組。

 

List<Integer>[] lists = new ArrayList<Integer>(); // 報錯看以下示範代碼// Not really allowed.List<String>[] lsa = new List<String>[10];      //1Object o = lsa;Object[] oa = (Object[]) o;List<Integer> li = new ArrayList<Integer>();li.add(new Integer(3));// Unsound, but passes run time store checkoa[1] = li;// Run-time error: ClassCastException.String s = lsa[1].get(0); 

 

如果允許泛型數組的存在(第 1 處代碼編譯通過),那麼在第 2 處代碼就會報出 ClassCastException,因為 lsa[1] 是 List<Integer> 。Java 設計者本著首要保證型別安全(type-safety)的原則,不允許泛型數組的存在,使得編譯期就可以檢查到這類錯誤。

 

解決方案

 

List<?>[] lsa = new List<?>[10];                //1Object o = lsa;Object[] oa = (Object[]) o;List<Integer> li = new ArrayList<Integer>();li.add(new Integer(3));// Correct.oa[1] = li;// Run time error, but cast is explicit.String s = (String) lsa[1].get(0);              //2

 

在第 1 處,用 ? 取代了確定的參數類型。根據萬用字元的定義以及 Java 類型擦除的保留上界原則,在 2 處 lsa[1].get(0) 取出的將會是 Object,所以需要程式員做一次顯式的類型轉換。

 

還有一種通過反射的方式來實現,使用 java.util.reflect.Array,可以不使用萬用字元,而達到泛型數組的效果。

 

List<String>[] lsa = (List<String>[])Array.newInstance(ArrayList.class, 4);     //1Object o = lsa;Object[] oa = (Object[]) o;List<Integer> li = new ArrayList<Integer>();li.add(new Integer(3));// Correct.oa[1] = li;// Run time error, but cast is explicit.String s = lsa[1].get(0);                                  //2

 

可以看到,利用 Array.newInstance() 產生了泛型數組,這裡沒有使用任何萬用字元,在第 2 處也沒有做顯式的類型轉換,但是在第 1 處,仍然存在顯式類型轉換。

 

所以要想使用泛型數組,要求程式員必須執行一次顯示的類型轉換,也就是將類型檢查的問題從編譯器交給了程式員。但是呢,泛型的設計初衷就是編譯器會協助我們檢查資料類型。你說矛盾不矛盾!

 

參考資料:

http://www.importnew.com/24029.html

51982979

www.cnblogs.com/wxw7blog/p/7517343.html

www.cnblogs.com/keyi/p/6068921.html

52058708

相關文章

Alibaba Cloud 10 Year Anniversary

With You, We are Shaping a Digital World, 2009-2019

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。