Java泛型是JDK1.5加入的新特性。泛型是指參數化的能力。可以定義帶泛型的類型的類或者方法,編譯時期編譯器會用具體的類型來代替它。Java泛型有泛型類、泛型介面和泛型方法。泛型的主要優點是能夠在編譯時期而不是在運行時期就檢測出錯誤。 泛型的出現 在JDK1.5之前,java.lang.Compa ...
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 T
- ? super T
第一種稱為非受限制通配,和? 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語言程式設計 進階篇