三石說:java 基礎 之 泛型

三石說:java 基礎 之 泛型

在沒有介紹泛型的使用時想想我們在哪些地方使用泛型的居多,那麼一定是集合中。在前面的集合中已經看到了使用到泛型了,今天來看下泛型具體的概念及用法。

泛型:

要使程式碼能夠應用於“某種具體的型別而不是一個具體的介面或類”。增加了泛型支援後的集合,完全可以記住集合中元素的型別,並可以在編譯時檢查集合中元素的型別,如果試圖向集合中新增不滿足型別要求的物件,編譯器就會提示錯誤。增加泛型後的集合,可以讓程式碼更加簡潔,程式更加健壯(Java泛型可以保證如果程式在編譯時沒有發出警告,執行時就不會產生ClassCastException異常)。除此之外,Java泛型還增強了列舉類、反射等方面的功能

當建立帶泛型宣告的自定義類,為該類定義構造器時,構造器名還是原來的類名,不要增加泛型宣告。

例如,為Apple類定義構造器,其構造器名依然是Apple,而不是

Apple

呼叫該構造器時卻可以使用Apple的形式,當然應該為T形參傳入實際的型別引數。Java 7提供了菱形語法,允許省略<>中的型別實參。

元組:

將一組物件直接打包儲存於其中的一個單一的物件。這個容器物件允許讀取其中的元素,但是不允許對其存放新的物件(這也稱為:資料傳送物件)

比如一個二維的元組:

public class TwoTuple{    public final A first;    public final B second;    public TwoTuple(A a, B b){ first = a; second = b;}     public String toStirng() {        return “(”+first+“,”+second+“)”;    }}

三石說:java 基礎 之 泛型

用泛型類構造一個堆疊類:

下面的例子使用了末端哨兵來判斷堆疊何時為空,這個末端哨兵在構造LinkedStack時建立,然後每呼叫一次push()方法,就會建立一個Node物件,並將其連結到前一個Node物件。

public class LinkedStack {  private static class Node {    U item;    Node next;    Node() { item = null; next = null; }    Node(U item, Node next) {      this。item = item;      this。next = next;    }    boolean end() { return item == null && next == null; }  }  private Node top = new Node(); // End sentinel  public void push(T item) {    top = new Node(item, top);  }      public T pop() {    T result = top。item;    if(!top。end())      top = top。next;    return result;  }  public static void main(String[] args) {    LinkedStack lss = new LinkedStack();    for(String s : “Phasers on stun!”。split(“ ”))      lss。push(s);    String s;    while((s = lss。pop()) != null)      System。out。println(s);  }} /* Output:stun!onPhasers*///:~

三石說:java 基礎 之 泛型

泛型介面:

public interface Generator{T next();}

用泛型介面實現生成Fibonacci數列:

public class Fibonacci implements Generator {    private int count=0;    public Integer next(){return fib(count++);}    private int fib(int n){        if(n<2) return 1;        return fib(n-2)+fib(n-1);    }    public static void main(String[] args){        Fibonacci gen = new Fibonacci();        for(int i=0;i<18;i++){            System。out。println(gen。next()+“ ”);        }    }}/* output1 1 2 3 5 8 13 21 34 55 89 144 233 377 。。。

泛型方法:

泛型方法使得該方法能夠獨立於類而產生變化。無論何時,只要你能做到,就應該儘量使用泛型方法。也就是說如果使用泛型方法可以取代整個類泛型化,那麼就應該使用泛型方法,因為他可以使得事情更清楚明白。

定義泛型方法:修飾符 返回引數 方法名(接收引數){}

public class MyUtils{泛型中的型別轉換,在未知型別時不能進行強轉:用下面方式應該先判斷List<?>[] lsa=new ArrayList<?>[10];Object[] oa=(Object[]) lsa;List li=new ArrayList();li。add(new Integer(3));oa[1]=li;Object target=lsa[1]。get(0);if (target instanceof String){// 下面程式碼安全了String s=(String) target;}使用泛型構建一個Set的使用工具(交集,差集):import java。util。*;public class Sets {  // 將兩個set合併為一個set    public static  Set union(Set a, Set b) {    Set result = new HashSet(a);    result。addAll(b);    return result;  }  // 返回共有的交集  public static   Set intersection(Set a, Set b) {    Set result = new HashSet(a);    result。retainAll(b);    return result;  }      // 從superset移除subset包含的元素    // Subtract subset from superset:  public static  Set  difference(Set superset, Set subset) {    Set result = new HashSet(superset);    result。removeAll(subset);    return result;  }  // 返回交集之外的所有元素  // Reflexive——everything not in the intersection:  public static  Set complement(Set a, Set b) {    return difference(union(a, b), intersection(a, b));  }} ///:~

泛型邊界萬用字元

三石說:java 基礎 之 泛型

泛型的上限:?extends 型別。

泛型的下限:?super 型別。

如果要從集合中讀取型別T的資料,並且不能寫入 可以使用 ? extends 萬用字元;(Producer Extends)

如果要從集合中寫入型別T的資料,並且不需要讀取,可以使用 ? super 萬用字元;(Consumer Super)

// 標明是一個泛型方法。public static void copy(Collection dest , Collection<? extends T> src){。。 。} //①這裡的?只能為T的子類 帶有子類限定的可以從泛型讀取//public static T copy(Collection<? super T> dest , Collection src){。 。。} //② ?只能為T的父類或者T 帶有子類限定的可以從泛型寫入}如果既要存又要取,那麼就不要使用任何萬用字元

上界的list只能get不能add,下屆的list只能add不能get

編譯器可以支援像上轉型,不支援像下轉型。

PECS參考:https://blog。51cto。com/flyingcat2013/1616068

泛型擦除:

在泛型程式碼內部,無法獲取任何有關泛型引數型別的資訊。java使用泛型擦除,比如List 和List 在執行時都是相同的型別,均被擦除為“原生”型別即List。 泛型擦除就是被擦除為父類。保留了型別的上限

比如:

List

list

=

new

ArrayList<>();

//型別被擦除了,保留的是型別的上限,String的上限就是Object

List

list1 =

list

泛型的面試題

Q&A Java中的泛型是什麼 ? 使用泛型的好處是什麼?

在集合中儲存物件並在使用前進行型別轉換是多麼的不方便。泛型防止了那種情況的發生。它提供了編譯期的型別安全,確保你只能把正確型別的物件放入 集合中,避免了在執行時出現ClassCastException

Q&A Java的泛型是如何工作的 ? 什麼是型別擦除 ?

泛型是透過型別擦除來實現的,編譯器在編譯時擦除了所有型別相關的資訊,所以在執行時不存在任何型別相關的資訊。例如 List在執行時僅用一個List來表示。這樣做的目的,是確保能和Java 5之前的版本開發二進位制類庫進行相容。你無法在執行時訪問到型別引數,因為編譯器已經把泛型型別轉換成了原始型別。

Q&A什麼是泛型中的限定萬用字元和非限定萬用字元 ?

限定萬用字元對型別進行了限制。有兩種限定萬用字元,一種是它透過確保型別必須是T的子類來設定型別的上界,另一種是它透過確保型別必須是T的父類來設定型別的下界。泛型型別必須用限定內的型別來進行初始化,否則會導致編譯錯誤。另一方面表 示了非限定萬用字元,因為可以用任意型別來替代

Q&A List 上界 和List 下界 之間有什麼區別 ?

上界的list只能get不能add,下屆的list只能add不能get

編譯器可以支援像上轉型,不支援像下轉型。

Q&A 你可以把List傳遞給一個接受List引數的方法嗎?

因為List可以儲存任何型別的物件包括String, Integer等等,而List卻只能用來儲存Strings。

List objectList;List stringList;objectList = stringList; //compilation error incompatible typespublic static void main(String[] args) {        List stringList = new ArrayList<>();        List objectList = new ArrayList<>();        stringList。add(“add”);        stringList。add(“123”);        objectList。add(“123”);        objectList。add(“234”);        // 讓objectList轉為stringList,編譯錯誤stringList必須是接收的List List之間不能轉換。        // stringList= objectList; // 編譯錯誤,List List 是沒有任何關係的。        // objectList=stringList; // 編譯錯誤        List<?> list = new ArrayList<>();        list = stringList;        System。out。println(list);        list = objectList;        System。out。println(list);        // list。add(“sss”); // 編譯器不允許這樣使用    }

在泛型的面試中很大一部分會出現泛型邊界問題,以及泛型的擦除。這是常常會問及的,所以我們需要格外的注意。

三石說:java 基礎 之 泛型