1、導讀
泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。Generic有“類的,屬性的”之意,在Java中代表泛型。泛型作為一種安全機制而產生。
2、為何需要泛型?
我們知道集合(Collection、Map之類的容器)是用來存儲任意對象(Object)的一系列“容器類或者接口”,注意這里的“任意對象”,就是指我們可以在這些類或接口中存放任意類型的對象,但這些對象在存儲之前都需要統一向上轉型為Object類型(因為Object類是所以類的的父類),比如類ArrayList就屬於“集合類”,我們可以用這個類來存儲任意對象:
package bean; import java.util.ArrayList; public class ArrayListDemo { public static void main(String[] args) { //首先我們創建一個ArrayList對象 ArrayList al = new ArrayList(); //往"容器"添加元素:我們添加了字符串"哈哈",數字2,boolean類型true,最后我們甚至添加了自己。 al.add("哈哈"); al.add(2); al.add(true); al.addAll(al); //由於ArrayList類覆蓋了toString(),這里我們可以直接打印查看效果: System.out.println(al); } }
最后打印出來的字符串是:[哈哈, 2, true, 哈哈, 2, true]
值得注意的是:這里的2不是int而是Integer,因為ArrayList只能存對象,而“添加它自己”addAll(al)的實質是將al中的所有元素再存入al中,正如打印效果顯示的一樣,我們成功添加了類型不一樣的對象:有String,有Integer、還有Boolean。當然,你可以添加任意你想要添加的對象。因為這些對象在存入“容器”al中時會全部轉型為Object類型,有沒有覺得這樣的“容器”用起來會很爽,什么東西都能往里放。但是,便捷往往會付出相應代價。
我們將各種類型的元素存入“容器”中當然不是讓它睡上一覺,我們需要將存儲的元素取出使用。對於這些“容器”,我們一般使用迭代器Iterator來獲取每一個元素。(這里不進行迭代器使用說明,只需理解迭代器是取出"容器"中元素的工具就行).
假設現在我們需要對我們上面創建的“容器”al中的元素進行操作:打印容器中字符串的長度,即“哈哈”的長度。
1 package bean; 2 3 import java.util.ArrayList; 4 import java.util.Iterator; 5 6 public class ArrayListDemo { 7 public static void main(String[] args) { 8 //首先我們創建一個ArrayList對象,並添加對象 9 ArrayList al = new ArrayList(); 10 al.add("哈哈"); 11 al.add(2); 12 al.add(true); 13 14 //使用迭代器Iterator進行遍歷,並打印容器中字符串的長度: 15 //注:next()方法是按照容器的存儲順序,逐一取出第一個到最后一個對象。 16 Iterator i = al.iterator(); 17 while(i.hasNext()){ 18 String s = (String) i.next(); 19 System.out.println(s.length()); 20 } 21 } 22 23 }
這是運行結果:
我們來進行代碼分析:while循環中,先用i.next()方法取出第一個元素"哈哈",然后裝換為String類型(因為存進去的時候是Object類型,使用的使用要向下轉型),所以接下來打印了“哈哈”的長度=2。接着循環:迭代器取出第二個元素:2,但這是Integer類型不能強制裝換為String類型,所以拋出異常:ClassCastException:類型轉換異常。
這就是“能存萬物”所帶來的代價:我們在使用“容器”存儲對象並調用時就換存在“類型轉換異常”的安全隱患,但這需要在運行后才能得知,編譯時無法發現。所以我們該如何解決這個問題呢?我們發現問題在於“類型轉換異常”,所以我們只要保證“容器”中存儲的是同一類型的元素就不會出現這個異常了。類似數組的定義一樣:如 int[] 就表示專門用來存儲int類型數據的數組,String[] 就是用來存儲String類型數據的數組。我們只要將“容器”標上相應類型的標簽就可以了。看到這里,你應該已經理解了泛型的由來,這就是泛型的設計:引入類型參數的概念,即對對象類型進行申明。
3、泛型的定義格式
格式為:類名<類型名>,如:
1 package bean; 2 3 import java.util.ArrayList; 4 import java.util.Iterator; 5 6 public class ArrayListDemo { 7 public static void main(String[] args) { 8 //首先我們創建一個ArrayList對象,並申明泛型類型為String類型:只能存儲String類型的對象 9 ArrayList<String> al = new ArrayList<String>(); 10 al.add("哈哈"); 11 al.add("asdafa"); 12 al.add("tobeyourself"); 13 14 //使用迭代器Iterator進行遍歷,並打印容器中字符串的長度:這里迭代器也使用了泛型 15 Iterator<String> i = al.iterator(); 16 while(i.hasNext()){ 17 String s = (String) i.next(); 18 System.out.println(s+"的長度為="+s.length()); 19 } 20 } 21 22 }
運行結果為:
3、泛型的好處
除了解決上面的安全隱患即拋出異常問題,泛型的類或接口在取出對象時將不需要再進行向下類型轉換,因為存儲的時候就是該類型 。
另外,泛型的使用讓安全問題在編譯時就報錯而不是運行后拋出異常,這樣便於程序員及時准確地發現問題。
4、什么時候使用泛型?
一般類或接口在api文檔中的定義中帶有<E>標識的,在使用的時候都需要使用泛型機制。如ArrayList<E>、Iterator<E>。
5、泛型的擦除
泛型是運用在編譯時期的技術:編譯時編譯器會按照<類型名>的類型對容器中的元素進行檢查,檢查不匹配,就編譯失敗。如果全部檢查成功,則編譯通過,但,編譯通過后產生的.class文件中並沒有<類型名>這個標識,即類文件中沒有泛型,這就是泛型的擦除。
一句話總結就是:在.java文件運用泛型技術時,編譯器在文件編譯通過后自動擦除泛型標識。
由於泛型的擦除,類文件中沒有泛型機制,同時也沒有使用向下類型轉換,那么為何運行時無異常?
6、泛型的補償
編譯器在擦除泛型后,會自動將類型轉換為原定義的"泛型",這樣就不必再做向下類型轉換了。
泛型的擦除和補償 這兩個機制都是編譯器內部自動完成的,了解其原理即可。
7、泛型的應用
7.1【泛型類】
所謂的泛型類就是運用到泛型技術的類,如上面講到的ArrayList<E>,Iterator<E>等都是java中的泛型類,這些類都是Java已經定義好的泛型類,直接使用就可以了。但有時候我們會遇到這樣的問題:
假設我們現在有兩個自定義類:Worker類和Student類,現在我們需要一個工具類Tool來獲取Worker對象和Student對象,並能對對象進行操作。
分析:
我們可能會想到將Worker和Student類作為Tool類的成員變量,以此來實現對這兩個類的操作。但這樣有一個問題,就是如果這不是兩個類而是很多類,甚至說無數個類,即Tool類可以操作任意類,這種通過添加成員變量來實現調用對象的方法顯然不可行。注意這個“添加任意對象”,這不就是上面說的ArrrayList<E>這些類所具有的特點嗎?泛型類就是為了解決“添加任意對象”而產生的,這里提到的ArrayList<E>屬於已定義泛型類(Java中自帶的),這里我們要用到Tool類來“存儲任意對象”,所以要將Tool類定義為泛型類,這就是根據需求自己設計的自定義泛型類:
首先我們先簡單的定義一下:Worker類和Student類:
Worker類:
1 package bean; 2 3 public class Worker { 4 private String name; 5 private int age; 6 Worker(){ 7 8 } 9 Worker(String name,int age){ 10 this.name = name; 11 this.age = age; 12 } 13 public String getName() { 14 return name; 15 } 16 public void setName(String name) { 17 this.name = name; 18 } 19 public int getAge() { 20 return age; 21 } 22 public void setAge(int age) { 23 this.age = age; 24 } 25 26 }
Student類:
1 package bean; 2 3 public class Student { 4 private String name; 5 private String sex; 6 Student(){ 7 8 } 9 Student(String name,String sex){ 10 this.name = name; 11 this.sex = sex; 12 } 13 public String getName() { 14 return name; 15 } 16 public void setName(String name) { 17 this.name = name; 18 } 19 public String getSex() { 20 return sex; 21 } 22 public void setSex(String sex) { 23 this.sex = sex; 24 } 25 26 }
現在我們需要將Tool類定義為泛型類:(注意格式)
1 package bean; 2 3 public class Tool<E> { 4 private E e; 5 6 public Tool(E e1){ 7 this.e = e1; 8 } 9 10 public E getE() { 11 return e; 12 } 13 public void setE(E e) { 14 this.e = e; 15 } 16 17 }
這里我們寫的很簡單,但已經滿足需求了,重點在於泛型類的寫法:Tool<E>,這里的E字母可以寫你自己喜歡的代碼,這也就是類型參數的應用,E相當於一個類型參數,代表了Tool<E>可以傳入任意對象,下面我們具體使用來看看效果:
1 package bean; 2 3 class Tooltest{ 4 public static void main(String[] args) { 5 //假設有兩個對象:一個學生,一個工人 6 Student t1 = new Student("張三","男"); 7 Worker w1 = new Worker("李四",20); 8 9 //現在我們使用Tool類來調用t1和w1 10 Tool<Student> ts = new Tool<Student>(t1); 11 Tool<Worker> tw = new Tool<Worker>(w1); 12 13 //打印查看效果 14 System.out.println("使用ts調用t1中的數據:"+ts.getE().getName()+":"+ts.getE().getSex()); 15 System.out.println("使用tw調用w1中的數據:"+tw.getE().getName()+":"+tw.getE().getAge()); 16 } 17 }
打印結果為:
使用ts調用t1中的數據:張三:男
使用tw調用w1中的數據:李四:20
7.2【泛型方法】
泛型方法類似於靜態類的設計,一般的方法傳入的參數是固定類型的,如public void show(int i){}這個方法的參數類型固定為int,但是泛型方法可以傳入指定的參數類型。一般泛型定義有兩種形式:
1)使用泛型類的參數類型來定義(常用):
如泛型類Tool<QQ>,它的泛型參數即為QQ。那么泛型方法可以這樣寫:public void show(QQ qq){}.
2)使用自定義的參數類型來定義:
如果我們需要自定義參數類型,那么我們把泛型參數放在方法上就可以了(放在返回值類型之前):public <AA> void show(AA aa){}。
注:靜態方法不能訪問類的泛型,如果需要泛型,我們只能使用方法2),在方法上使用泛型即可:public static <AA> void show(AA aa){}
7.3【泛型接口】
和上面一樣的道理,當我們不確定使用對象的類型時,運用泛型就可以解決問題,泛型接口和泛型類的使用是一樣一樣的。
我們只需要注意,在實現泛型接口時有兩種情況:這里假設有泛型接口interf<AA>,它的實現類是Tool。
1)確定實現的泛型接口的參數類型:
假設Tool類需要String類型的參數,那么實現可以直接寫:class Tool implements interf<String>{}即可。
2)不確定實現的泛型接口的參數類型:
這時候我們需要泛型類來實現classTool<BB> implements interf<BB>{}.
8、泛型の通配符:?
當我們不確定傳入的對象類型時我們就可以使用?來代替。“?”即泛型通配符。
9、泛型的限定
我們知道使用泛型類時:如果明確參數類型,那么泛型就代表一種類型;如果使用通配符?,那么泛型就代表任意類型。但有時候我們希望指定某些類型(不是一個,也不要所有)能作為參數類型,這應該怎么辦呢?
Java中利用泛型的限定解決了這個問題,即泛型的限定。我們只需要按這樣的格式書寫:
上限:<? extends E>表示參數類型是E及其所有子類。
下限:<? super E>表示參數類型是E及其所有超類(即父類)。
10、總結
泛型即“類型參數化”,用的多了就懂了。