1.為什么需要泛型
泛型在Java中有很重要的地位,網上很多文章羅列各種理論,不便於理解,本篇將立足於代碼介紹、總結了關於泛型的知識。希望能給你帶來一些幫助。
先看下面的代碼:
- List list = new ArrayList();
- list.add("CSDN_SEU_Cavin");
- list.add(100);
- for (int i = 0; i < list.size(); i++) {
- String name = (String) list.get(i); //取出Integer時,運行時出現異常
- System.out.println("name:" + name);
- }
本例向list類型集合中加入了一個字符串類型的值和一個Integer類型的值。(這樣合法,因為此時list默認的類型為Object類型)。在之后的循環中,由於忘記了之前在list中也加入了Integer類型的值或其他原因,運行時會出現java.lang.ClassCastException異常。為了解決這個問題,泛型應運而生。
2.泛型的使用
Java泛型編程是JDK1.5版本后引入的。泛型讓編程人員能夠使用類型抽象,通常用於集合里面。
只要在上例中將第1行代碼改成如下形式,那么就會在編譯list.add(100)時報錯。
- List<String> list = new ArrayList<String>();
通過List<String>,直接限定了list集合中只能含有String類型的元素,從而在上例中的第6行中,無須進行強制類型轉換,因為集合能夠記住其中元素的類型信息,編譯器已經能夠確認它是String類型了。
3.泛型只在編譯階段有效
看下面的代碼:
- AyyayList<String> a = new ArrayList<String>();
- ArrayList b = new ArrayList();
- Class c1 = a.getClass();
- Class c2 = b.getClass();
- System.out.println(a == b); //true
上面程序的輸出結果為true。所有反射的操作都是在運行時的,既然為true,就證明了編譯之后,程序會采取去泛型化的措施,也就是說Java中的泛型,只在編譯階段有效。在編譯過程中,正確檢驗泛型結果后,會將泛型的相關信息擦出,並且在對象進入和離開方法的邊界處添加類型檢查和類型轉換的方法。也就是說,成功編譯過后的class文件中是不包含任何泛型信息的。泛型信息不會進入到運行時階段。
上述結論可通過下面反射的例子來印證:
- ArrayList<String> a = new ArrayList<String>();
- a.add("CSDN_SEU_Cavin");
- Class c = a.getClass();
- try{
- Method method = c.getMethod("add",Object.class);
- method.invoke(a,100);
- System.out.println(a);
- }catch(Exception e){
- e.printStackTrace();
- }
因為繞過了編譯階段也就繞過了泛型,輸出結果為:
- [CSDN_SEU_Cavin, 100]
4.泛型類和泛型方法
如下,我們看一個泛型類和方法的使用例子,和未使用泛型的使用方法進行了對比,兩者輸出結果相同,在這里貼出來方便讀者體會兩者的差異。泛型接口的例子有興趣可以去找一些資料,這里就不贅述了。
(1)使用泛型的情況
- public static class FX<T> {
- private T ob; // 定義泛型成員變量
- public FX(T ob) {
- this.ob = ob;
- }
- public T getOb() {
- return ob;
- }
- public void showTyep() {
- System.out.println("T的實際類型是: " + ob.getClass().getName());
- }
- }
- public static void main(String[] args) {
- FX<Integer> intOb = new FX<Integer>(100);
- intOb.showTyep();
- System.out.println("value= " + intOb.getOb());
- System.out.println("----------------------------------");
- FX<String> strOb = new FX<String>("CSDN_SEU_Calvin");
- strOb.showTyep();
- System.out.println("value= " + strOb.getOb());
- }
(2)不使用泛型的情況
- public static class FX {
- private Object ob; // 定義泛型成員變量
- public FX(Object ob) {
- this.ob = ob;
- }
- public Object getOb() {
- return ob;
- }
- public void showTyep() {
- System.out.println("T的實際類型是: " + ob.getClass().getName());
- }
- }
- public static void main(String[] args) {
- FX intOb = new FX(new Integer(100));
- intOb.showTyep();
- System.out.println("value= " + intOb.getOb());
- System.out.println("----------------------------------");
- FX strOb = new FX("CSDN_SEU_Calvin");
- strOb.showTyep();
- System.out.println("value= " + strOb.getOb());
- }
輸出結果均為:
- T的實際類型是: java.lang.Integer
- value= 100
- ----------------------------------
- T的實際類型是: java.lang.String
- value= CSDN_SEU_Calvin
5.通配符
為了引出通配符的概念,先看如下代碼:
- List<Integer> ex_int= new ArrayList<Integer>();
- List<Number> ex_num = ex_int; //非法的
上述第2行會出現編譯錯誤,因為Integer雖然是Number的子類,但List<Integer>不是List<Number>的子類型。
假定第2行代碼沒有問題,那么我們可以使用語句ex_num.add(newDouble())在一個List中裝入了各種不同類型的子類,這顯然是不可以的,因為我們在取出List中的對象時,就分不清楚到底該轉型為Integer還是Double了。
因此,我們需要一個在邏輯上可以用來同時表示為List<Integer>和List<Number>的父類的一個引用類型,類型通配符應運而生。在本例中表示為List<?>即可。下面這個例子也說明了這一點,注釋已經寫的很清楚了。
- public static void main(String[] args) {
- FX<Number> ex_num = new FX<Number>(100);
- FX<Integer> ex_int = new FX<Integer>(200);
- getData(ex_num);
- getData(ex_int);//編譯錯誤
- }
- public static void getData(FX<Number> temp) { //此行若把Number換為“?”編譯通過
- //do something...
- }
- public static class FX<T> {
- private T ob;
- public FX(T ob) {
- this.ob = ob;
- }
- }
6.上下邊界
看了下面這個上邊界的例子就明白了,下界FX<? supers Number>的形式就不做過多贅述了。
- public static void main(String[] args) {
- FX<Number> ex_num = new FX<Number>(100);
- FX<Integer> ex_int = new FX<Integer>(200);
- getUpperNumberData(ex_num);
- getUpperNumberData(ex_int);
- }
- public static void getUpperNumberData(FX<? extends Number> temp){
- System.out.println("class type :" + temp.getClass());
- }
- public static class FX<T> {
- private T ob;
- public FX(T ob) {
- this.ob = ob;
- }
- }
7.泛型的好處
(1)類型安全。
通過知道使用泛型定義的變量的類型限制,編譯器可以更有效地提高Java程序的類型安全。
(2)消除強制類型轉換。
消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。所有的強制轉換都是自動和隱式的。
(3)提高性能。
- Lits list1 = new ArrayList();
- list1.add("CSDN_SEU_Cavin ");
- String str1 = (String)list1.get(0);
- List<String> list2 = new ArrayList<String>();
- list2.add("CSDN_SEU_Cavin ");
- String str2 = list2.get(0);
對於上面的兩段程序,由於泛型所有工作都在編譯器中完成,javac編譯出來的字節碼是一樣的(只是更能確保類型安全),那么何談性能提升呢?是因為在泛型的實現中,編譯器將強制類型轉換插入生成的字節碼中,但是更多類型信息可用於編譯器這一事實,為未來版本的 JVM 的優化帶來了可能。
8.泛型使用的注意事項
(1)泛型的類型參數只能是類類型(包括自定義類),不能是簡單類型。
(2)泛型的類型參數可以有多個。
(3)不能對確切的泛型類型使用instanceof操作。如下面的操作是非法的,編譯時會出錯。
- if(ex_num instanceof FX<Number>){
- }
(4)不能創建一個確切的泛型類型的數組。下面使用Sun的一篇文檔的一個例子來說明這個問題:
- List<String>[] lsa = new List<String>[10]; // Not really allowed.
- Object o = lsa;
- Object[] oa = (Object[]) o;
- List<Integer> li = new ArrayList<Integer>();
- li.add(new Integer(3));
- oa[1] = li; // Unsound, but passes run time store check
- String s = lsa[1].get(0); // Run-time error: ClassCastException.
這種情況下,由於JVM泛型的擦除機制,在運行時JVM是不知道泛型信息的,所以可以給oa[1]賦上一個ArrayList<Integer>而不會出現異常,但是在取出數據的時候卻要做一次類型轉換,所以就會出現ClassCastException,如果可以進行泛型數組的聲明,上面說的這種情況在編譯期將不會出現任何的警告和錯誤,只有在運行時才會出錯。
下面采用通配符的方式是被允許的:
- List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
- Object o = lsa;
- Object[] oa = (Object[]) o;
- List<Integer> li = new ArrayList<Integer>();
- li.add(new Integer(3));
- oa[1] = li; // Correct.
- Integer i = (Integer) lsa[1].get(0); // OK