我相信很多人跟我一樣還未學習過范型的概念就開始使用范型的實例,最典型的就是集合框架。為了 進一步深入了解范型,這一次通過幾個簡單的例子來說明范型的注意事項。
一.沒有范型的世界
所有的java類都派生自java.lang.Object ,這意味著所有的java對象都可以轉換成Object,聽起來似乎 很美妙,但事實並非如此。舉個例子,假設現在需要一伙人去排隊,要求只有學生可以參與進來,但是如 果對於這個隊伍沒有條件限定的話,那就意味著我們不想要的一些群體也會進入大軍之中,這不利於管理 。再如下面一段沒有使用范型的代碼:
List string=new ArrayList();
string.add("我是第一個元素");
string.add("我是第二個元素");
當要從中獲取一個成員時,得到的卻是java.lang.Object的一個實例,那麼如果我們要使用String類 型的對象還要進行強轉型,這無疑增加了代碼的冗雜度。不過幸好,我們有了范型。
二. 范型類型簡介
像方法一樣,范型類型也可以接受參數,聲明范型類型要使用尖括號將類型變量列表括起來。例如聲 明一個List對象:
List<E> mylist;
為了將范型類型實例化,要在聲明它的時候傳遞相同的參數列表,例如,為了創建一個使用String的 ArrayList,要將String放在一對尖括號中進行傳遞:
List<String> string=new ArrayList<String>();
不過,java7版本之後,可以在參數化類的構造器中顯式的傳入參數,上面一 段代碼可以改為如下的表達方式:
List<String> string=new ArrayList< >();
毫無疑問范型類型也可以指定Object類型,但是范型類型卻不可以是java.lang.Throwable的直接或間 接子類,因為它在運行時拋出異常,因此無法查看在編譯時會拋出什麼異常。
下面一段代碼就對使用了范型與未使用范型的List進行了比較:
Java代碼
import java.util.ArrayList; import java.util.List; public class GenericListTest { public static void main(String args[]){ //沒有使用范型的隊列 List stringList=new ArrayList(); stringList.add("元素1"); stringList.add("元素2"); //需要強轉型 String s1=(String)stringList.get(0); System.out.println(s1); //使用范型的隊列 List<String> list=new ArrayList<String>(); list.add("元素3"); list.add("元素4"); //不需要強轉型 String s2=list.get(0); System.out.println(s2); } }
提示:使用范型類型時,是在編譯時進行類型檢查的。
這裡值得關注的地方在於,范型類型本身也是一個類型,它還可以用作類型變量。
如下的一段代碼演示:
Java代碼
import java.util.ArrayList; import java.util.List; public class ListOfList { public static void main(String args[]){ List<String> list=new ArrayList<>(); list.add("我是list中的對象"); //list中的元素 System.out.println(list.get(0)); //創建封裝了list的List對象 List<List<String>> listOflist=new ArrayList<>(); listOflist.add(list); //獲取list當中的元素 String s=listOflist.get(0).get(0); System.out.println(s); } }
范型類型可以接受不止一個類型變量,那就是我們非常熟悉的Map集合,相信大家也已經相當熟悉了吧
三. 使用“?”通配符
前面所提到的,如果聲明一個List<aType>,那麼List將使用aType的實例,並且可以保存以下任 意一種類型:
1. aType的一個實例
2. aType的子類的一個實例,如果aType是類的話
3. 實現aType的類的一個實例,如果aType是接口的話
但是,注意范型類型本身也是一個java類型,如果你不理解的話,下面這段代碼將會讓你更加了解這 句話的含義:
Java代碼
import java.util.ArrayList; import java.util.List; public class AllowedTypeTest { public static void main(String args[]){ List<String> myList=new ArrayList<String>(); doIt(myList); } private static void doIt(List<Object> l){ } }
編譯產生了錯誤,按理說,String是Object的一個子類,但是進行了范型類型封裝之後 List<String>就不再是List<Object>的一個實例了。
為了解決這個問題,我們就要用到通配符“?”,如下:
Java代碼
import java.util.ArrayList; import java.util.List; public class WildCardTest { private static void doIt(List<?> l){ for(Object element:l){ System.out.print(element); } } public static void main(String args[]){ //字符型List List<String> stringList=new ArrayList<String>(); stringList.add("Hello"); stringList.add("World"); doIt(stringList); System.out.println(); //整型List List<Integer> intList=new ArrayList<Integer>(); intList.add(100); intList.add(200); doIt(intList); } }
代碼中doIt方法裡面的List<?>表示的是任意類型的一個List,但要注意的是,在聲明或者創建 一個范型類型時使用通配符是不合法的,如:
List<?> list=new ArrayList<?>();
編譯會出錯。
四. 在方法中使用有界通配符
通過上面的描述,你已經知道了可以使用“?”通配符來解決類型匹配的問題,但是我們 在實際中碰到的問題並非這麼簡單。如果我們想定義一個Number實現子類的List,雖然我們可以用通配符 來解決List<Integer>與List<Double>類型不匹配的問題,但是無疑的,我們還可以放入很 多我們並不需要的實例如List<String>,這樣程序的類型安全性根本就沒有保障了,那麼我們就必 須需要一種規則來保證我們所定義的List實例是屬於Number的實現子類的,在這裡我們就要用到有界通配 符。
如下演示:
Java代碼
import java.util.ArrayList; import java.util.List; public class BoundedWildcardTest { public static void main(String args[]){ List<Integer> intList=new ArrayList<>(); intList.add(3); intList.add(30); System.out.println(getAverage(intList)); List<Double> doubleList=new ArrayList<>(); doubleList.add(3.0); doubleList.add(30.0); System.out.println(getAverage(doubleList)); } private static double getAverage(List<? extends Number> list){ double total=0.0; for(Number number:list){ total+=number.doubleValue(); } return total/list.size(); } }
五.編寫范型類型
編寫范型類型與其他類型並無太大差異,只是在類中的某處聲明需要用到的類型變量列表。下面就是 范型類的一個簡單示例:
Java代碼
public class MyGeneric<E> { E a; public MyGeneric(E a){ this.a=a; } public E get(){ return a; } public void set(E b){ this.a=b; } }
范型為我們的編碼過程做出了巨大的貢獻,提供了更加嚴格的類型檢查,並且在取得元素時無需再進 行類型轉換,真乃編碼之一大利器也。
查看本欄目