Java泛型

引入泛型的主要目標有如下幾點:

類型安全java

  • 泛型的主要目標是提升 Java 程序的類型安全
  • 編譯時期就能夠檢查出因 Java 類型不正確致使的 ClassCastException 異常
  • 符合越早出錯代價越小原則

消除強制類型轉換數組

  • 泛型的一個附帶好處是,使用時直接獲得目標類型,消除許多強制類型轉換
  • 所得即所需,這使得代碼更加可讀,而且減小了出錯機會

潛在的性能收益安全

  • 因爲泛型的實現方式,支持泛型(幾乎)不須要 JVM 或類文件更改
  • 全部工做都在編譯器中完成
  • 編譯器生成的代碼跟不使用泛型(和強制類型轉換)時所寫的代碼幾乎一致,只是更能確保類型安全而已

使用方式

  • 泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數。
  • 類型參數的意義是告訴編譯器這個集合中要存放實例的類型,從而在添加其餘類型時作出提示,在編譯時就爲類型安全作了保證。
  • 參數類型能夠用在類、接口和方法的建立中,分別稱爲泛型類泛型接口泛型方法

通配符

  • 有時候但願傳入的類型有一個指定的範圍,從而能夠進行一些特定的操做,這時候就是通配符邊界登場的時候了。

泛型中有三種通配符形式:框架

  • <?> 無限制通配符
  • <? extends E> extends關鍵字聲明瞭類型的上界,表示參數化的類型多是所指定的類型,或者是此類型的子類
  • <? super E> super關鍵字聲明瞭類型的下界,表示參數化的類型多是指定的類型,或者是此類型的父類

無限制通配符 <?>

  • 要使用泛型,可是不肯定或者不關心實際要操做的類型,可使用無限制通配符(尖括號裏一個問號,即 <?> ),表示能夠持有任何類型
  • 大部分狀況下,這種限制是好的,但這使得一些理應正確的基本操做都沒法完成。
private void swap(List<?> list, int i, int j){
    Object o = list.get(i);
    list.set(j,o);
}
  • 這個代碼看上去應該是正確的,但 Java 編譯器會提示編譯錯誤,set 語句是非法的。編譯器提示咱們把方法中的 List<?> 改爲List<Object> 。

? 和 Object 不同嗎?編輯器

  • List<?> 表示未知類型的列表,而 List<Object> 表示任意類型的列表。好比傳入 List<String> ,這時 List 的元素類型就是 String,想要往 List 裏添加一個 Object,這固然是不能夠的。

上界通配符 < ? extends E>

在類型參數中使用 extends 表示這個泛型中的參數必須是 E 或者 E 的子類,這樣有兩個好處:性能

  • 若是傳入的類型不是 E 或者 E 的子類,編譯不成功
  • 泛型中調用使用 E 的方法,要否則必須強轉成 E 才能調用

下界通配符 < ? super E>

在類型參數中使用 super 表示這個泛型中的參數必須是 E 或者 E 的父類。翻譯

private <E> void add(List<? super E> dst, List<E> src){
    for (E e : src) {
        dst.add(e);
    }
}
  • dst 類型 「大於等於」 src 的類型,這裏的「大於等於」是指 dst 表示的範圍比 src 要大,所以裝得下 dst 的容器也就能裝 src。向上轉型老是合理的。

通配符比較

  • 無限制通配符 <?> 和 Object 有些類似,用於表示無限制或者不肯定範圍的場景。<?> 至關於 <? extends Object>
  • < ? extends E> 用於靈活讀取,使得方法能夠讀取 E 或 E 的任意子類型的容器對象。
  • < ? super E> 用於靈活寫入或比較,使得對象能夠寫入父類型的容器,使得父類型的比較方法能夠應用於子類對象。
  • 爲了得到最大限度的靈活性,要在表示 生產者或者消費者 的輸入參數上使用通配符,使用的規則就是:生產者有上限消費者有下限PECS: producer-extends, costumer-super
  • T 的生產者的意思就是結果會返回 T (get),這就要求返回一個具體的類型,必須有上限纔夠具體;
  • T 的消費者的意思是要操做 T (set),這就要求操做的容器要夠大,因此容器須要是 T 的父類,即 super T;
private  <E extends Comparable<? super E>> E max(List<? extends E> e1){
        if (e1 == null){
            return null;
        }
        
        Iterator<? extends E> iterator = e1.iterator();
        E result = iterator.next();
        while (iterator.hasNext()){
            E next = iterator.next();
            if (next.compareTo(result) > 0){
                result = next;
            }
        }
        return result;
    }
  • 要進行比較,因此 E 須要是可比較的類,所以須要 extends Comparable<…>
  • Comparable<? super E> 要對 E 進行比較,即 E 的消費者,因此須要用 super
  • 而參數 List<? extends E> 表示要操做的數據是 E 的子類的列表。

類型擦除

Java 中的泛型和 C++ 中的模板有一個很大的不一樣:code

  • C++ 中模板的實例化會爲每一種類型都產生一套不一樣的代碼,這就是所謂的代碼膨脹。
  • Java 中並不會產生這個問題。虛擬機中並無泛型類型對象,全部的對象都是普通類。

  • 在 Java 中,泛型是 Java 編譯器的概念,用泛型編寫的 Java 程序和普通的 Java程序基本相同,只是多了一些參數化的類型同時少了一些類型轉換。
  • 實際上泛型程序也是首先被轉化成通常的、不帶泛型的 Java 程序後再進行處理的,編譯器自動完成了從 Generic Java 到普通Java 的翻譯,Java 虛擬機運行時對泛型基本一無所知。
  • 當編譯器對帶有泛型的java代碼進行編譯時,它會去執行類型檢查類型推斷,而後生成普通的不帶泛型的字節碼,這種普通的字節碼能夠被通常的 Java 虛擬機接收並執行,這在就叫作類型擦除(type erasure)。
  • 實際上不管你是否使用泛型,集合框架中存放對象的數據類型都是 Object
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass() == integers.getClass());//true

上面代碼輸出結果並非預期的 false,而是 true。其緣由就是泛型的擦除。對象

實現原理

  • Java 編輯器會將泛型代碼中的類型徹底擦除,使其變成原始類型。
  • 接着 Java編譯器會在這些代碼中加入類型轉換,將原始類型轉換成想要的類型。這些操做都是編譯器後臺進行,能夠保證類型安全。
  • 泛型就是一個語法糖,它運行時沒有存儲任何類型信息。

擦除致使的泛型不可變性

  • 協變:若是 A 是 B 的父類,而且 A 的容器(好比 List< A>)也是 B 的容(List< B > ) 的父類,則稱之爲協變的(父子關係保持一致)
  • 逆變:若是 A 是 B 的父類,可是 A 的容器 是 B 的容器的子類,則稱之爲逆變(放入容器就篡位了)
  • 不可變:不論 A B 有什麼關係,A 的容器和 B 的容器都沒有父子關係,稱之爲不可變。
  • Java 中數組是協變的,泛型是不可變的

若是想要讓某個泛型類具備協變性,就須要用到邊界。接口

總結

  • 泛型是經過擦除來實現的。所以泛型只在編譯時強化它的類型信息,而在運行時丟棄(或者擦除)它的元素類型信息。擦除使得使用泛型的代碼能夠和沒有使用泛型的代碼隨意互用。
  • 數組中不能使用泛型

Java 中 List<Object> 和原始類型 List 之間的區別?

  • 在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數的類型進行檢查
  • 經過使用 Object 做爲類型,能夠告知編譯器該方法能夠接受任何類型的對象,好比String 或 Integer
  • 能夠把任何帶參數的類型傳遞給原始類型 List,但卻不能把 List< String> 傳遞給接受 List< Object>的方法,由於泛型的不可變性,會產生編譯錯誤。

區分原始類型和泛型變量的類型

  • 在調用泛型方法的時候,能夠指定泛型,也能夠不指定泛型。
  • 在不指定泛型的狀況下,泛型變量的類型爲該方法中的幾種類型的同一個父類的最小級,直到Object。
  • 在指定泛型的時候,該方法中的幾種類型必須是該泛型實例類型或者其子類。
public class Test2{  
    public static void main(String[] args) {  
        /**不指定泛型的時候*/  
        int i=Test2.add(1, 2); //這兩個參數都是Integer,因此T爲Integer類型  
        Number f=Test2.add(1, 1.2);//這兩個參數一個是Integer,以風格是Float,因此取同一父類的最小級,爲Number  
        Object o=Test2.add(1, "asd");//這兩個參數一個是Integer,以風格是Float,因此取同一父類的最小級,爲Object  
  
                /**指定泛型的時候*/  
        int a=Test2.<Integer>add(1, 2);//指定了Integer,因此只能爲Integer類型或者其子類  
        int b=Test2.<Integer>add(1, 2.2);//編譯錯誤,指定了Integer,不能爲Float  
        Number c=Test2.<Number>add(1, 2.2); //指定爲Number,因此能夠爲Integer和Float  
    }  
      
    //這是一個簡單的泛型方法  
    public static <T> T add(T x,T y){  
        return y;  
    }  
}
  • 類型檢查就是針對引用的,誰是一個引用,用這個引用調用泛型方法,就會對這個引用調用的方法進行類型檢測,而無關它真正引用的對象。
相關文章
相關標籤/搜索