:notebook: 本文已歸檔到:「blog」html
:keyboard: 本文中的示例代碼已歸檔到:「javacore」java
JDK5 引入了泛型機制。git
爲何須要泛型呢?回答這個問題前,先讓咱們來看一個示例。程序員
public class NoGenericsDemo { public static void main(String[] args) { List list = new ArrayList<>(); list.add("abc"); list.add(18); list.add(new double[] {1.0, 2.0}); Object obj1 = list.get(0); Object obj2 = list.get(1); Object obj3 = list.get(2); System.out.println("obj1 = [" + obj1 + "]"); System.out.println("obj2 = [" + obj2 + "]"); System.out.println("obj3 = [" + obj3 + "]"); int num1 = (int)list.get(0); int num2 = (int)list.get(1); int num3 = (int)list.get(2); System.out.println("num1 = [" + num1 + "]"); System.out.println("num2 = [" + num2 + "]"); System.out.println("num3 = [" + num3 + "]"); } } // Output: // obj1 = [abc] // obj2 = [18] // obj3 = [[D@47089e5f] // Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer // at io.github.dunwu.javacore.generics.NoGenericsDemo.main(NoGenericsDemo.java:23) 複製代碼
示例說明:github
在上面的示例中,
List
容器沒有指定存儲數據類型,這種狀況下,能夠向List
添加任意類型數據,編譯器不會作類型檢查,而是默默的將全部數據都轉爲Object
。算法假設,最初咱們但願向
List
存儲的是整形數據,假設,某個傢伙不當心存入了其餘數據類型。當你試圖從容器中取整形數據時,因爲List
當成Object
類型來存儲,你不得不使用類型強制轉換。在運行時,纔會發現List
中數據不存儲一致的問題,這就爲程序運行帶來了很大的風險(無形傷害最爲致命)。編程
而泛型的出現,解決了類型安全問題。數組
泛型具備如下優勢:安全
泛型要求在聲明時指定實際數據類型,Java 編譯器在編譯時會對泛型代碼作強類型檢查,並在代碼違反類型安全時發出告警。早發現,早治理,把隱患扼殺於搖籃,在編譯時發現並修復錯誤所付出的代價遠比在運行時小。bash
未使用泛型:
List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0); 複製代碼
使用泛型:
List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast 複製代碼
經過使用泛型,程序員能夠實現通用算法,這些算法能夠處理不一樣類型的集合,能夠自定義,而且類型安全且易於閱讀。
泛型類型
是被參數化的類或接口。
泛型類的語法形式:
class name<T1, T2, ..., Tn> { /* ... */ } 複製代碼
泛型類的聲明和非泛型類的聲明相似,除了在類名後面添加了類型參數聲明部分。由尖括號(<>
)分隔的類型參數部分跟在類名後面。它指定類型參數(也稱爲類型變量)T1,T2,...和 Tn。
通常將泛型中的類名稱爲原型,而將 <>
指定的參數稱爲類型參數。
在泛型出現以前,若是一個類想持有一個能夠爲任意類型的數據,只能使用 Object
作類型轉換。示例以下:
public class Info { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } 複製代碼
public class Info<T> { private T value; public Info() { } public Info(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } @Override public String toString() { return "Info{" + "value=" + value + '}'; } } public class GenericsClassDemo01 { public static void main(String[] args) { Info<Integer> info = new Info<>(); info.setValue(10); System.out.println(info.getValue()); Info<String> info2 = new Info<>(); info2.setValue("xyz"); System.out.println(info2.getValue()); } } // Output: // 10 // xyz 複製代碼
在上面的例子中,在初始化一個泛型類時,使用 <>
指定了內部具體類型,在編譯時就會根據這個類型作強類型檢查。
實際上,不使用 <>
指定內部具體類型,語法上也是支持的(不推薦這麼作),以下所示:
public static void main(String[] args) { Info info = new Info(); info.setValue(10); System.out.println(info.getValue()); info.setValue("abc"); System.out.println(info.getValue()); } 複製代碼
示例說明:
上面的例子,不會產生編譯錯誤,也能正常運行。但這樣的調用就失去泛型類型的優點。
public class MyMap<K,V> { private K key; private V value; public MyMap(K key, V value) { this.key = key; this.value = value; } @Override public String toString() { return "MyMap{" + "key=" + key + ", value=" + value + '}'; } } public class GenericsClassDemo02 { public static void main(String[] args) { MyMap<Integer, String> map = new MyMap<>(1, "one"); System.out.println(map); } } // Output: // MyMap{key=1, value=one} 複製代碼
public class GenericsClassDemo03 { public static void main(String[] args) { Info<String> info = new Info("Hello"); MyMap<Integer, Info<String>> map = new MyMap<>(1, info); System.out.println(map); } } // Output: // MyMap{key=1, value=Info{value=Hello}} 複製代碼
接口也能夠聲明泛型。
泛型接口語法形式:
public interface Content<T> { T text(); } 複製代碼
泛型接口有兩種實現方式:
public class GenericsInterfaceDemo01 implements Content<Integer> { private int text; public GenericsInterfaceDemo01(int text) { this.text = text; } @Override public Integer text() { return text; } public static void main(String[] args) { GenericsInterfaceDemo01 demo = new GenericsInterfaceDemo01(10); System.out.print(demo.text()); } } // Output: // 10 複製代碼
public class GenericsInterfaceDemo02<T> implements Content<T> { private T text; public GenericsInterfaceDemo02(T text) { this.text = text; } @Override public T text() { return text; } public static void main(String[] args) { GenericsInterfaceDemo02<String> gen = new GenericsInterfaceDemo02<>("ABC"); System.out.print(gen.text()); } } // Output: // ABC 複製代碼
泛型方法是引入其本身的類型參數的方法。泛型方法能夠是普通方法、靜態方法以及構造方法。
泛型方法語法形式以下:
public <T> T func(T obj) {} 複製代碼
是否擁有泛型方法,與其所在的類是不是泛型沒有關係。
泛型方法的語法包括一個類型參數列表,在尖括號內,它出如今方法的返回類型以前。對於靜態泛型方法,類型參數部分必須出如今方法的返回類型以前。類型參數能被用來聲明返回值類型,而且能做爲泛型方法獲得的實際類型參數的佔位符。
使用泛型方法的時候,一般沒必要指明類型參數,由於編譯器會爲咱們找出具體的類型。這稱爲類型參數推斷(type argument inference)。類型推斷只對賦值操做有效,其餘時候並不起做用。若是將一個泛型方法調用的結果做爲參數,傳遞給另外一個方法,這時編譯器並不會執行推斷。編譯器會認爲:調用泛型方法後,其返回值被賦給一個 Object 類型的變量。
public class GenericsMethodDemo01 { public static <T> void printClass(T obj) { System.out.println(obj.getClass().toString()); } public static void main(String[] args) { printClass("abc"); printClass(10); } } // Output: // class java.lang.String // class java.lang.Integer 複製代碼
泛型方法中也可使用可變參數列表
public class GenericVarargsMethodDemo { public static <T> List<T> makeList(T... args) { List<T> result = new ArrayList<T>(); Collections.addAll(result, args); return result; } public static void main(String[] args) { List<String> ls = makeList("A"); System.out.println(ls); ls = makeList("A", "B", "C"); System.out.println(ls); } } // Output: // [A] // [A, B, C] 複製代碼
Java 語言引入泛型是爲了在編譯時提供更嚴格的類型檢查,並支持泛型編程。不一樣於 C++ 的模板機制,Java 泛型是使用類型擦除來實現的,使用泛型時,任何具體的類型信息都被擦除了。
那麼,類型擦除作了什麼呢?它作了如下工做:
<>
的內容。好比 T get()
方法聲明就變成了 Object get()
;List<String>
就變成了 List
。若有必要,插入類型轉換以保持類型安全。讓咱們來看一個示例:
public class GenericsErasureTypeDemo { public static void main(String[] args) { List<Object> list1 = new ArrayList<Object>(); List<String> list2 = new ArrayList<String>(); System.out.println(list1.getClass()); System.out.println(list2.getClass()); } } // Output: // class java.util.ArrayList // class java.util.ArrayList 複製代碼
示例說明:
上面的例子中,雖然指定了不一樣的類型參數,可是 list1 和 list2 的類信息倒是同樣的。
這是由於:使用泛型時,任何具體的類型信息都被擦除了。這意味着:
ArrayList<Object>
和ArrayList<String>
在運行時,JVM 將它們視爲同一類型。
Java 泛型的實現方式不太優雅,但這是由於泛型是在 JDK5 時引入的,爲了兼容老代碼,必須在設計上作必定的折中。
泛型不能用於顯式地引用運行時類型的操做之中,例如:轉型、instanceof 操做和 new 表達式。由於全部關於參數的類型信息都丟失了。當你在編寫泛型代碼時,必須時刻提醒本身,你只是看起來好像擁有有關參數的類型信息而已。
正是因爲泛型時基於類型擦除實現的,因此,泛型類型沒法向上轉型。
向上轉型是指用子類實例去初始化父類,這是面向對象中多態的重要表現。
Integer
繼承了 Object
;ArrayList
繼承了 List
;可是 List<Interger>
卻並不是繼承了 List<Object>
。
這是由於,泛型類並無本身獨有的 Class
類對象。好比:並不存在 List<Object>.class
或是 List<Interger>.class
,Java 編譯器會將兩者都視爲 List.class
。
List<Integer> list = new ArrayList<>(); List<Object> list2 = list; // Erorr 複製代碼
有時您可能但願限制可在參數化類型中用做類型參數的類型。類型邊界
能夠對泛型的類型參數設置限制條件。例如,對數字進行操做的方法可能只想接受 Number
或其子類的實例。
要聲明有界類型參數,請列出類型參數的名稱,而後是 extends
關鍵字,後跟其限制類或接口。
類型邊界的語法形式以下:
<T extends XXX>
複製代碼
示例:
public class GenericsExtendsDemo01 { static <T extends Comparable<T>> T max(T x, T y, T z) { T max = x; // 假設x是初始最大值 if (y.compareTo(max) > 0) { max = y; //y 更大 } if (z.compareTo(max) > 0) { max = z; // 如今 z 更大 } return max; // 返回最大對象 } public static void main(String[] args) { System.out.println(max(3, 4, 5)); System.out.println(max(6.6, 8.8, 7.7)); System.out.println(max("pear", "apple", "orange")); } } // Output: // 5 // 8.8 // pear 複製代碼
示例說明:
上面的示例聲明瞭一個泛型方法,類型參數
T extends Comparable<T>
代表傳入方法中的類型必須實現了 Comparable 接口。
類型邊界能夠設置多個,語法形式以下:
<T extends B1 & B2 & B3>
複製代碼
注意:extends 關鍵字後面的第一個類型參數能夠是類或接口,其餘類型參數只能是接口。
示例:
public class GenericsExtendsDemo02 { static class A { /* ... */ } interface B { /* ... */ } interface C { /* ... */ } static class D1 <T extends A & B & C> { /* ... */ } static class D2 <T extends B & A & C> { /* ... */ } // 編譯報錯 static class E extends A implements B, C { /* ... */ } public static void main(String[] args) { D1<E> demo1 = new D1<>(); System.out.println(demo1.getClass().toString()); D1<String> demo2 = new D1<>(); // 編譯報錯 } } 複製代碼
類型通配符
通常是使用 ?
代替具體的類型參數。例如 List<?>
在邏輯上是 List<String>
,List<Integer>
等全部 List<具體類型實參>
的父類。
可使用**上界通配符
**來縮小類型參數的類型範圍。
它的語法形式爲:<? extends Number>
public class GenericsUpperBoundedWildcardDemo { public static double sumOfList(List<? extends Number> list) { double s = 0.0; for (Number n : list) { s += n.doubleValue(); } return s; } public static void main(String[] args) { List<Integer> li = Arrays.asList(1, 2, 3); System.out.println("sum = " + sumOfList(li)); } } // Output: // sum = 6.0 複製代碼
**下界通配符
**將未知類型限制爲該類型的特定類型或超類類型。
注意:上界通配符和下界通配符不能同時使用。
它的語法形式爲:<? super Number>
public class GenericsLowerBoundedWildcardDemo { public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 5; i++) { list.add(i); } } public static void main(String[] args) { List<Integer> list = new ArrayList<>(); addNumbers(list); System.out.println(Arrays.deepToString(list.toArray())); } } // Output: // [1, 2, 3, 4, 5] 複製代碼
無界通配符有兩種應用場景:
語法形式:<?>
public class GenericsUnboundedWildcardDemo { public static void printList(List<?> list) { for (Object elem : list) { System.out.print(elem + " "); } System.out.println(); } public static void main(String[] args) { List<Integer> li = Arrays.asList(1, 2, 3); List<String> ls = Arrays.asList("one", "two", "three"); printList(li); printList(ls); } } // Output: // 1 2 3 // one two three 複製代碼
前面,咱們提到:泛型不能向上轉型。可是,咱們能夠經過使用通配符來向上轉型。
public class GenericsWildcardDemo { public static void main(String[] args) { List<Integer> intList = new ArrayList<>(); List<Number> numList = intList; // Error List<? extends Integer> intList2 = new ArrayList<>(); List<? extends Number> numList2 = intList2; // OK } } 複製代碼
擴展閱讀:Oracle 泛型文檔
Pair<int, char> p = new Pair<>(8, 'a'); // 編譯錯誤 複製代碼
public static <E> void append(List<E> list) { E elem = new E(); // 編譯錯誤 list.add(elem); } 複製代碼
public class MobileDevice<T> { private static T os; // error // ... } 複製代碼
public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) { // 編譯錯誤 // ... } } 複製代碼
List<Integer> li = new ArrayList<>(); List<Number> ln = (List<Number>) li; // 編譯錯誤 複製代碼
List<Integer>[] arrayOfLists = new List<Integer>[2]; // 編譯錯誤 複製代碼
// Extends Throwable indirectly class MathException<T> extends Exception { /* ... */ } // 編譯錯誤 // Extends Throwable directly class QueueFullException<T> extends Throwable { /* ... */ // 編譯錯誤 複製代碼
public static <T extends Exception, J> void execute(List<J> jobs) { try { for (J job : jobs) // ... } catch (T e) { // compile-time error // ... } } 複製代碼
public class Example { public void print(Set<String> strSet) { } public void print(Set<Integer> intSet) { } // 編譯錯誤 } 複製代碼
泛型一些約定俗成的命名: