Java泛型詳解。本站提示廣大學習愛好者:(Java泛型詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是Java泛型詳解正文
Java泛型是JDK1.5加入的新特性。泛型是指參數化的能力。可以定義帶泛型的類型的類或者方法,編譯時期編譯器會用具體的類型來代替它。Java泛型有泛型類、泛型接口和泛型方法。泛型的主要優點是能夠在編譯時期而不是在運行時期就檢測出錯誤。
泛型的出現在JDK1.5之前,java.lang.Comparable的定義如下所示:
public interface Comparable { public int comparaTo(Object o); }
在JDK1.5之後,泛型的定義如下:
public interface Comparable<T> { public int comparaTo(T o); }
這裡的<T>表示形式形式泛型類型,之後可以用一個實際的具體類型來替換它。替換泛型稱為泛型實例化。按照慣例,像E或T這樣的單個字母用於表示一個形式泛型類型。為了看到泛型的具體好處,我們來看具體的實例。
圖1
圖2
由於Date實現了Comparable接口,由Java的多態特性,我們可以用父類的指針指向子類,也就是我們可以new一個Date類型賦值給我們的Comparable接口類型。當我們調用Comparable接口的comparaTo()方法時。由於圖1沒有指定泛型,編譯時期不會出現提示,但是在運行時期會報出:java.lang.String cannot be cast to java.util.Date的錯誤,提示信息提示String類型不能轉換為Date進行比較。而使用了泛型了圖2,在編譯期間就提示錯誤,因為傳遞給compareTo方法的參數必須是Date類型。由於這個錯誤是在編譯器而不是運行期被檢測到,因而泛型使程序更加可靠。
現在我們來實現一個線性表list,命名為GenericArrayList,可以接收泛型數據。該類實現了add()添加元素的方法,size()獲取元素個數的方法,和獲取指定下標元素的get()方法。
public class GenericArrayList<E> { Object[] objects=new Object[10]; int index=0; public GenericArrayList(){ System.out.println("構造函數"); } public void add(E o){ if(index==objects.length){ Object[] newObjects=new Object[objects.length*2]; System.arraycopy(objects, 0, newObjects, 0, objects.length); objects=newObjects; } objects[index]=o; index++; } public int size(){ return index; } public E get(int index) { return (E) objects[index]; } }
下面代碼片段將向list中添加三個城市名,然後再將城市名依次取出。
GenericArrayList<String> ga1 = new GenericArrayList<String>(); ga1.add("北京"); ga1.add("貴陽"); ga1.add("重慶"); for(int i = 0; i < ga1.size(); i++) { System.out.println(ga1.get(i)); }
同樣的,可以向list中添加如數字10086,然後再將數字依次取出。
GenericArrayList<Integer> ga2 = new GenericArrayList<Integer>(); ga2.add(1); ga2.add(0); ga2.add(0); ga2.add(8); ga2.add(6); for(int i = 0; i < ga2.size(); i++) { System.out.println(ga2.get(i)); }
注意:
1.上面創建的兩個GenericArrayList對象ga1和ga2,他們創建的語法分別是:new GenericArrayList<String>()和new GenericArrayList<Integer>(),但是千萬不要認為我的GenericArrayList類中分別對應兩個這樣的構造方法。
public GenericArrayList<String>(){ System.out.println("構造函數"); }
public GenericArrayList<Integer>(){ System.out.println("構造函數"); }
而實際上,我的構造方法是在第7行定義的。
2.有時候泛型的參數有多個,那麼我們可以把所有的參數一起放在間括號裡面,如<E1,E2,E3>。
3.可以定義一個類或一個接口作為作為泛型或者接口的子類型。例如,在Java API中,java.lang.String類被定義為實現Comparable接口,如下所示:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
定義泛型方法:
public class Test2 { public static void main(String[] args) { Integer[] arr1 = {1, 0, 0, 8, 6, 1, 1}; Test2.<Integer>pint(arr1); String[] names = {"馬雲", "馬化騰", "李彥宏"}; Test2.<String>pint(names); } public static <E> void pint(E[] arr) { for(int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); } }
上訴代碼定義了打印數組的print方法,arr1是一個整型的數組,而arr2是一個字符串類型的數組,當他們調用print時,分別將數組的內容輸出。
為了調用泛型方法,需要將實際類型放在間括號作為方法名的前綴。如,
Test2.
假設我們要定義一個泛型方法,找出list中的最大值。那麼代碼可以參考如下:
public class Test3 { public static void main(String[] args) { GenericArrayList<Integer> ga = new GenericArrayList<Integer>(); ga.add(1); ga.add(2); ga.add(3); Test3.max(ga); } public static double max(GenericArrayList<Number> list) { double maxValue = list.get(0).doubleValue(); for(int i = 0; i < list.size(); i++) { double value = list.get(i).doubleValue(); if(value > maxValue) { maxValue = value; } } return maxValue; } }
首先new出一個list對象,並向list裡面添加元素1,2,3,然後調用max方法。max方法的邏輯是依次取出list裡面的元素,與我們的標記maxValue對比,如果大於maxValue當前元素值,就把當前元素值賦值給maxValue。
但是,上面的代碼編譯會錯誤,因為ga不是GenericArrayList<Number&glt; 的對象,所以不能調用max()方法。
盡管Integer是Number的子類(除Integer之外,還有Short,Byte,Long,Float,Double等也是Number的子類),但是GenericArrayList<Integer>不是GenericArrayList<Number>的子類。
解決的方案是使用通配泛型。只需要把max的方法頭改寫如下即可:
public static <E> double max(GenericArrayList<? extends Number> list)
通配泛型有三種形式:
第一種稱為非受限制通配,和? extends Oject是一樣的。第二種稱為受限制通配,表示T或T的一個未知子類型。第三種稱為下限通配,表示T或T的的一個父類。
第二種通配泛型上面的案例已經使用過,下面我們來看第一種類型。案例如下:
public class Test4 { public static void main(String[] args) { GenericArrayList<Integer> ga = new GenericArrayList<Integer>(); ga.add(1); ga.add(2); ga.add(3); Test4.print(ga); GenericArrayList<Person> ga1 = new GenericArrayList<Person>(); ga1.add(new Person("馬雲")); ga1.add(new Person("李彥宏")); ga1.add(new Person("馬化騰")); Test4.print(ga1); } public static void print(GenericArrayList<?> list) { for(int i = 0; i < list.size(); i++) { System.out.print(list.get(i) + " "); } System.out.println(); } }
Person類定義如下:
public class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person [name=" + name + "]"; } }
為了輸出我們的Person對象,需要對Person的toString()方法重寫。main方法中new了兩個GenericArrayList對象,一個的實際參數是Integer型list,另一個是Person對象的list。案例的輸出如下:
構造函數
1 2 3
構造函數
Person [name=馬雲] Person [name=李彥宏] Person [name=馬化騰]
這裡如果把?換成Object則報錯。眾所周知:無論是Integer還是Person都繼承自Object,因為Obejct是所有類的父類。但是,GenericArrayList<Person>不是GenericArrayList<Object>的子類。
現在來看看第三種通配泛型的用法。
public class Test5 { public static void main(String[] args) { GenericArrayList<Object> ga = new GenericArrayList<Object>(); ga.add(1); ga.add(2); ga.add(3); GenericArrayList<Person> ga1 = new GenericArrayList<Person>(); ga1.add(new Person("馬雲")); ga1.add(new Person("李彥宏")); ga1.add(new Person("馬化騰")); Test5.add(ga1, ga); //調用Test4的泛型輸出方法 Test4.print(ga); } //該方法的功能是將list1添加到list2 public static <T> void add(GenericArrayList<T> list1, GenericArrayList<? super T> list2) { for(int i = 0; i < list1.size(); i++) { // list1.add(list2.get(i)); list2.add(list1.get(i)); } } }
上訴代碼,我們想將一個Person的List追加到Integer的List中去。先創建ga對象,該對象的實際類型是Object,賦值1,2,3的時候自動裝箱編程Integer,屬於Object的子類。ga1的實際類型是Person,屬於Object,符合Person super Object。控制台輸出如下:
構造函數
構造函數
1 2 3 Person [name=馬雲] Person [name=李彥宏] Person [name=馬化騰]
控制台的第一行和第二行“構造函數”是在我們new GenericArrayList對象的時候打印的。第三行,成功的將合並後的list打印出來,前三個元素是整型元素,後三個為Person對象的屬性值。
類型擦除泛型是使用一種稱為類型擦除的方法來實現的,編譯器使用泛型類型信息來編譯代碼,然後會查擦除它。在生成的Java字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱為類型擦除。如在代碼中定義的List<Object>和List<String>等類型,在編譯之後都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。
ArrayList<String> list1 = new ArrayList<String>(); ArrayList<Integer> list2 = new ArrayList<Integer>();
盡管編譯時期,ArrayList<String>和ArrayList<Integer> 是兩個不同的類型,但是編譯成字節碼之後,只有一中類型ArrayList。因此以下兩行輸入都為true;
System.out.println(list1 instanceof ArrayList); System.out.println(list2 instanceof ArrayList);
參考資料:
Java深度歷險(五)——Java泛型
Java語言程序設計 進階篇