一、泛型的概念
Java5引入參數化類型(Parameterized Type)的概念,也稱為泛型(Generic)。泛型:就是允許在定義類、接口、方法時使用類型形參。這個類型形參將在聲明變量、創建對象、調用方法時動態指定,即傳入實際的類型參數(也叫傳入類型實參)。傳入的類型實參的類型必須是引用類型。
二、泛型類
2.1、定義泛型類
public class A<T> { // 泛型類:定義類的時候指定類型形參T,在類里面T就可以當成類型使用 private T a; public T getA() { return a; } public void setA(T a) { this.a = a; } }
2.2、繼承泛型類的幾種方式
class B1 extends A<String> {} class B2<E> extends A<String> {} class B3<E> extends A<E> {} class B4<E1, E2> extends A<E1> {}
2.3、使用泛型類
public static void main(String[] args) { B1 b1 = new B1(); b1.setA("b1"); System.out.println(b1.getA()); A<String> a1 = new B1(); a1.setA("a1"); System.out.println(a1.getA()); //B2<?> b2 = new B2<String>(); //B2<String> b2:聲明變量時已經指定了B2的類型形參E為String, //new B2<>():創建對象時可以使用菱形語法(泛型推斷) B2<String> b2 = new B2<>();//菱形語法 b2.setA("b2"); System.out.println(b2.getA()); // 無法通過A<String>推斷出B2的類型形參E的類型,不可以使用菱形語法 A<String> a2 = new B2<Object>(); a2.setA("a2"); System.out.println(a2.getA()); B3<String> b3 = new B3<>();//菱形語法 b3.setA("b3"); System.out.println(b3.getA()); A<String> a3 = new B3<>();//菱形語法 a3.setA("a3"); System.out.println(a3.getA()); }
2.4、JDK7新特性:菱形語法(泛型推斷)
菱形語法(泛型推斷):從JDK 7 開始,Java允許在構造器后不需要帶完整的泛型信息,只要給出一對尖括號<>即可,Java可以推斷出尖括號里面應該是什么類型。
比如:List<String> list = new ArrayList<>();
解釋:ArrayList和list的關系是:
public interface List<E>
public class ArrayList<E> implements List<E>
左邊List<String> list定義變量時已經指定了ArrayList類型形參E為String,所以通過泛型推斷知道new ArrayList<>()的尖括號里面類型是String。
三、泛型方法
3.1、泛型方法與方法重載
下面程序 fun(2) 調用的是 public static void fun(int a) 方法。如果沒有定義public static void fun(int a) 方法,就會去調用泛型方法public static <T> void fun(T a)。
package com.oy.type; public class Demo01 { public static void main(String[] args) { fun(2); // int: 2 fun(Integer.valueOf(2)); // Integer: 2 fun(20.0); // 泛型方法: 20.0 fun("hello"); // 泛型方法: hello } public static <T> void fun(T a) { // 泛型方法,<T>定義在返回值前面 System.out.println("泛型方法: " + a); } public static void fun(Integer a) { System.out.println("Integer: " + a); } public static void fun(int a) { System.out.println("int: " + a); } }
3.2、不能通過new T()來創建對象
public class Demo02 { public static void main(String[] args) { String str = null; try { str = fun2(String.class); } catch (Exception e) { e.printStackTrace(); } System.out.println("str = " + str); // str = Integer i = null; try { // Integer類沒有空參構造,調用Class類的newInstance()方法時拋InstantiationException異常 i = fun2(Integer.class); } catch (Exception e) { e.printStackTrace(); } System.out.println("i = " + i); // i = null } public static <T> T fun2(Class<T> clazz) throws Exception { try { return clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); throw new Exception("創建對象出錯!"); } } }
對於泛型方法定義時的類型形參T,不能通過new T()來創建對象,所以要創建類型T對應的對象,需要傳遞Class<T> clazz參數。
四、泛型構造器
//定義泛型類,給類型形參T傳值時必須是Number類或Number類的子類 public class A<T extends Number> { private T a; public A() {} public A(T a) { this.a = a; } // 泛型構造器 public <E> A(E e) { System.out.println("調用泛型構造器:" + e); } public T getA() { return a; } public void setA(T a) { this.a = a; } public static void main(String[] args) { A<Integer> a1 = new A<Integer>(); a1.setA(1); System.out.println(a1.getA()); // 構造器傳入int類型的參數,則調用構造器public A(T a) A<Integer> a2 = new A<Integer>(2); System.out.println(a2.getA()); // 構造器傳入字符串類型的參數,則調用泛型構造器 A<Integer> a3 = new A<Integer>("字符串1"); System.out.println(a3.getA()); // 指定了泛型構造器類型形參E為String類型 A<Integer> a4 = new <String> A<Integer>("字符串1"); System.out.println(a3.getA()); // 使用菱形語法:左邊指定了類型形參T為Integer,可以通過泛型推斷,得知右邊<>里面應該是什么類型 A<Integer> a5 = new A<>("字符串2"); // 下面錯誤。如果顯示指定了泛型構造器的類型形參的實際類型,則不可以使用菱形語法 //A<Integer> a6 = new <String> A<>("字符串2");// 使用菱形語法 } }
五、類型形參的限制(指定類型形參的上限)
//定義泛型類,給類型形參T傳值時必須是Number類或Number類的子類 public class A<T extends Number> { private T a; public T getA() { return a; } public void setA(T a) { this.a = a; } }
// 定義泛型類,T必須是Number類或Number類的子類,並且必須實現Serializable接口 // 可以指定多個接口上限,但如果指定類上限,類要位於接口前面 public class A<T extends Number & Serializable> { private T a; public T getA() { return a; } public void setA(T a) { this.a = a; } }
public class Demo02 { public static void main(String[] args) { fun(2); fun(20.0); //fun("hello");
} public static <T extends Number> void fun(T a) { // 泛型方法,T必須是Number類或其子類
System.out.println(a); } }
// 錯誤 public class A<T super Number> {}
六、並不存在所謂的泛型類
實際上,深入到類的class文件時發現,並不存在所謂的泛型類。下面代碼中list1的類型是java.util.ArrayList,而不是java.util.ArrayList<String>。因此,不管泛型的實際類型是什么,運行時總有相同的類(或說字節碼文件*.class)。
ArrayList<String> list1 = new ArrayList<>(); ArrayList<Integer> list2 = new ArrayList<>(); System.out.println(list1.getClass().getName()); // java.util.ArrayList System.out.println(list2.getClass().getName()); // java.util.ArrayList System.out.println(list1.getClass() == list2.getClass()); // true
不管為泛型的類型形參傳入什么類型實參,對於java來說,它們依然被當成同一個類來處理,在內存中也只占用一塊內存空間,因此在靜態方法、靜態初始化代碼塊或者靜態變量的聲明和初始化中不允許使用類型形參。下面程序演示了這種錯誤:
public class A<T> { static T info; // 錯誤,不能在靜態變量聲明中使用類型形參 T age; public void foo(T msg) {} // 下面代碼錯誤,不能在靜態方法中使用類型形參 public static void bar(T msg) {} }
由於並不會真正生成泛型類,所以instanceof運算符后不能使用泛型類。下面程序演示了這種錯誤:
List<String> list = new ArrayList<>(); if (list instanceof List) { // list instanceof List: true System.out.println("list instanceof List: " + (list instanceof List)); } // 下面代碼錯誤,instanceof后面不能使用泛型 if (list instanceof List<String>) {}