Java泛型編程是JDK1.5版本後引入的.泛型讓編程人員可以使用類型抽象,一般用於集合裏面.最大的特色是泛型中的屬性能夠由外部決定. java
類的泛型聲明格式: 編程
class 類名稱<泛型類型, 泛型類型... ...>{} 數組
示例: 安全
List myIntList=new LinkedList(); myIntList.add(new Integer(0)); Integer x=(Integer)myIntList.iterator().next(); // next()返回的是Object,因此必須強轉注意第 3行 ,存儲在 List裏面的對象類型是 Integer,可是在返回列表中元素時 ,仍是必須強制轉換類型 ,這是爲何呢?緣由在於 ,編譯器只能保證迭代器的 next()方法返回的是 Object類型的對象 ,爲保證 Integer變量的類型安全 ,因此必須強制轉換 .
這種轉換不只顯得混亂,更可能致使類型轉換異常ClassCastException,爲保證操做安全,減小轉換髮生錯誤, 而泛型使取出變得很是容易,不須要再使用向下轉型.這就是泛型設計的初衷. 數據結構
示例: app
List<Integer> myIntList=newLinkedList<Integer>(); myIntList.add(new Integer(0)); Integer x=myIntList.iterator().next();在第 1行代碼中 指定List 中存儲的對象類型爲Integer,這樣在獲取列表中的對象時 ,沒必要強制轉換類型了 .
下面是一個引用自java.util包中的接口List和Iterator的定義,其中用到了泛型技術. 函數
示例: 學習
public interface List<E> { //類型由外部決定 void add(E x); //add的類型與設置類型保持一致 Iterator<E> iterator(); } public interface Iterator<E> { //類型由外部決定 E next(); //返回的類型與傳入類型保持一致 boolean hasNext(); }這跟原生類型沒有什麼區別 ,只是在接口後面加入了一個尖括號 ,尖括號裏面是一個類型參數 .
List<Integer>表示List中的類型參數E被替換成Integer類型.和以下代碼等價: ui
public interface IntegerList { void add(Integer x) Iterator<Integer> iterator(); }
類型擦除指的是經過類型參數合併,將泛型類型實例關聯到同一份字節碼上.在使用時若是沒有指定泛型類型,則表示擦除泛型類型.擦除後按Object接收.通常不要擦除泛型,由於沒有什麼實際意義,同時以保證操做的安全性. this
編譯器只爲泛型類型生成一份字節碼,並將其實例關聯到這份字節碼上,所以泛型類型中的靜態變量是全部實例共享的.故一個static方法,沒法訪問泛型類的類型參數,由於類尚未實例化,因此,若static方法須要使用泛型能力,必須使其成爲泛型方法.
類型擦除的關鍵在於從泛型類型中清除類型參數的相關信息,而且在必要的時候添加類型檢查和類型轉換的方法.在使用泛型時,任何具體的類型都被擦除,惟一知道的是你在使用一個對象.好比:List<String>和List<Integer>在運行事實上是相同的類型.他們都被擦除成他們的原生類型,即List.
由於編譯的時候會有類型擦除,因此不能經過同一個泛型類的實例來區分方法,由於類型擦除後,兩個方法都是List類型的參數,所以並不能根據泛型類的類型來區分方法.
示例:
public class Info<T> { private T msg; public Info(T msg) { this.msg = msg; } public T getMsg() { return msg; } public void setMsg(T msg) { this.msg = msg; } } public class GenDemo { public static void main(String[] args) { Info info0 = new Info(1) ; // 沒有指定泛型類型,但寫代碼時會有警告 Info<Object> info1 = new Info<Object>(1) ; // 沒有指定泛型類型 Info<Object> info2 = new Info<Object>("1") ; // 沒有指定泛型類型 //注意的是 info0, info1, info2的類型都是Info,並非Integer和String型 System.out.println(info0.getClass() == info1.getClass()); //True } }
那麼這就有個問題了 ,既然在編譯的時候會在方法和類中擦除實際類型的信息 ,那麼在返回對象時又是如何知道其具體類型的呢?如 List<String>編譯後會擦除掉 String信息 ,那麼在運行時經過迭代器返回 List中的對象時 ,又是如何知道 List中存儲的是 String類型對象呢?
擦除在方法體中移除了類型信息,因此在運行時的問題就是邊界:即對象進入和離開方法的地點,這正是編譯器在編譯期執行類型檢查並插入轉型代碼的地點.泛型中的全部動做都發生在邊界處:對傳遞進來的值進行額外的編譯期檢查,並插入對傳遞出去的值的轉型.
泛型不支持咱們以前接觸過的向上轉型,爲了完全理解泛型,這裏看個例子:(Apple爲Fruit的子類)
示例:
List<Apple> apples = new ArrayList<Apple>(); // 向上轉型 List<Fruit> fruits = apples;
第 1行代碼顯然是對的 ,可是第 2行在編譯的時候會出錯 .這會讓人比較納悶的是一個蘋果是水果 ,爲何一箱蘋果就不是一箱水果了呢?能夠這樣考慮 ,假定第 2行代碼沒有問題 ,那麼咱們可使用語句 fruits.add(new Strawberry())在 fruits中加入草莓了 ,可是這樣的話 ,一個 List中裝入了各類不一樣類型的子類水果 ,這顯然是不能夠的 ,由於咱們在取出 List中的水果對象時 ,就分不清楚到底該轉型爲蘋果仍是草莓了 .
一般來講,若是Foo是Bar的子類型,G是一種帶泛型的類型,則G<Foo>不是G<Bar>的子類型.這也是泛型學習裏面最讓人容易混淆的一點.
先看一個打印集合中全部元素的代碼.
示例:
//不使用泛型 void printCollection(Collection c) { Iterator i=c.iterator(); for (k=0;k < c.size();k++) { System.out.println(i.next()); } }
示例:
//使用泛型 void printCollection(Collection<Object> c) { for (Object e:c) { System.out.println(e); } }很容易發現 ,使用泛型的版本只能接受元素類型爲 Object類型的集合 ,如 ArrayList<Object>();若是是 ArrayList<String>,則會編譯時出錯 .
Collection<Object>並非全部集合的超類.而老版本能夠打印任何類型的集合,那麼如何改造新版本以便它能接受全部類型的集合呢?這個問題能夠經過使用通配符來解決.修改後的代碼以下所示:
示例:
//使用通配符?表示能夠接收任何元素類型的集合做爲參數 void printCollection(Collection<?> c) { for (Object e:c) { System.out.println(e); } }這裏使用了通配符?指定可使用任何類型的集合做爲參數 .讀取的元素使用了 Object類型來表示 ,這是安全的 ,由於全部的類都是 Object的子類 .
這裏就又出現了另一個問題,若是試圖往使用通配符?的集合中加入(add)對象,就會在編譯時出現錯誤.
須要注意的是,這裏無論加入什麼類型的對象都會出錯.這是由於通配符?表示該集合存儲的元素類型未知,能夠是任何類型.往集合中加入元素須要是一個未知元素類型的子類型,正由於該集合存儲的元素類型未知,因此咱們無法向該集合中添加任何元素.惟一的例外是null,由於null是全部類型的子類型,因此儘管元素類型不知道,可是null必定是它的子類型.
示例:
Collection<?> c=new ArrayList<String>(); c.add(new Object()); //compile time error,無論加入什麼對象都出錯,除了null外。 c.add(null); //OK另外一方面 ,咱們能夠從 List<?> lists中獲取對象 ,雖然不知道 List中存儲的是什麼類型 ,可是能夠確定的是存儲的類型必定是 Object的子類型 ,因此能夠用 Object類型來獲取值 .如 for(Object obj: lists),這是合法的 .
1)?extends通配符
即泛型的上限,如今假定有一個畫圖的應用,能夠畫各類形狀的圖形,如矩形和圓形等.
示例:
public abstract class Shape { public abstract void draw(Canvas c); } public class Circle extends Shape { private int x,y,radius; public void draw(Canvas c) { ... } } public class Rectangle extends Shape { private int x,y,width,height; public void draw(Canvasc) { ... } }爲了畫出集合中全部的形狀 ,咱們能夠定義一個函數 ,該函數接受帶有泛型的集合類對象做爲參數 .可是不幸的是 ,咱們只能接收元素類型爲 Shape的 List對象 ,而不能接收類型爲 List<Cycle>的對象 ,這在前面已經說過 .爲了解決這個問題 ,因此有了 邊界通配符的概念 .這裏能夠採用 public void drawAll(List<? extends Shape> shapes)來知足條件 ,這樣就能夠接收元素類型爲 Shape子類型的列表做爲參數了 .
示例:
//原始版本 public void drawAll(List<Shape> shapes) { for (Shape s:shapes) { s.draw(this); } }
示例:
//使用邊界通配符的版本 public void drawAll(List<?exends Shape> shapes) { for (Shape s:shapes) { s.draw(this); } }這裏就又有個問題要注意了 ,若是咱們但願在 List<? exends Shape> shapes中加入一個矩形對象 ,以下所示:
shapes.add(0, new Rectangle()); //compile-time error那麼這時會出現一個編譯時錯誤 ,緣由在於:咱們只知道 shapes中的元素時 Shape類型的子類型 ,具體是什麼子類型咱們並不清楚 ,因此咱們不能往 shapes中加入任何類型的對象 . 不過咱們在取出其中對象時, 可使用Shape類型來取值 ,由於雖然咱們不知道列表中的元素類型具體是什麼類型 ,可是咱們確定的是它必定是 Shape類的子類型 .
2)?super通配符
即泛型的下限.
示例:
List<Shape> shapes = new ArrayList<Shape>(); List<? super Cicle> cicleSupers = shapes; cicleSupers.add(new Cicle()); //OK, subclass of Cicle also OK cicleSupers.add(new Shape()); //ERROR這表示 cicle Supers列表存儲的元素爲 Cicle的超類 ,所以咱們能夠往其中加入 Cicle對象或者 Cicle的子類對象 ,可是不能加入 Shape對象 .這裏的緣由在於列表 cicle Supers存儲的元素類型爲 Cicle的超類 ,可是具體是 Cicle的什麼超類並不清楚 .可是咱們能夠肯定的是隻要是 Cicle或者 Circle的子類 ,則必定是與該元素類別兼容 .
3)邊界通配符總結
若想從一個數據類型裏獲取數據,使用 ? extends 通配符
若想把對象寫入一個數據結構裏,使用 ? super 通配符
若既想存,又想取,那就別用通配符.
考慮實現一個方法,該方法拷貝一個數組中的全部對象到集合中.下面是初始的版本:
示例:
static void fromArrayToCollection(Object[]a, Collection<?> c) { for (Object o:a) { c.add(o); //compile time error } }能夠看到顯然會出現編譯錯誤 ,緣由在以前有講過 ,由於集合 c中的類型未知 ,因此不能往其中加入任何的對象(固然 ,null除外) .解決該問題的一種比較好的辦法是使用泛型方法 ,以下所示:
示例:
static <T> void fromArrayToCollection(T[] a, Collection<T>c){ for(T o : a) { c.add(o);// correct } }注意泛型方法的格式 ,類型參數 <T>須要放在函數返回值以前 .而後在參數和返回值中就可使用泛型參數了 .具體一些調用方法的實例以下:
示例:
Object[] oa = new Object[100]; Collection<Object>co = new ArrayList<Object>(); fromArrayToCollection(oa, co);// T inferred to be Object String[] sa = new String[100]; Collection<String>cs = new ArrayList<String>(); fromArrayToCollection(sa, cs);// T inferred to be String fromArrayToCollection(sa, co);// T inferred to be Object Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number>cn = new ArrayList<Number>(); fromArrayToCollection(ia, cn);// T inferred to be Number fromArrayToCollection(fa, cn);// T inferred to be Number fromArrayToCollection(na, cn);// T inferred to be Number fromArrayToCollection(na, co);// T inferred to be Object fromArrayToCollection(na, cs);// compile-time error注意到咱們調用方法時並不須要傳遞類型參數 ,系統會自動判斷類型參數並調用合適的方法 .固然在某些狀況下須要指定傳遞類型參數 ,好比當存在與泛型方法相同的方法的時候(方法參數類型不一致) ,以下面的一個例子:
示例:
public <T> void go(T t) { System.out.println("generic function"); } public void go(String str) { System.out.println("normal function"); } public static void main(String[] args) { FuncGenric fg = new FuncGenric(); fg.go("haha");//打印normal function fg.<String>go("haha");//打印generic function fg.go(new Object());//打印generic function fg.<Object>go(new Object());//打印generic function }
如例子中所示 ,當不指定類型參數時 ,調用的是普通的方法 ,若是指定了類型參數 ,則調用泛型方法 .能夠這樣理解 ,由於泛型方法編譯後類型擦除 ,若是不指定類型參數 ,則泛型方法此時至關因而 public void go(Object t).而普通的方法接收參數爲 String類型 ,所以以 String類型的實參調用函數 ,確定會調用形參爲 String的普通方法了 .若是是以 Object類型的實參調用函數 ,則會調用泛型方法 .
1)方法重載
在JAVA裏面方法重載是不能經過返回值類型來區分的,好比代碼一中一個類中定義兩個以下的方法是不允許的.可是當參數爲泛型類型時,倒是能夠的.以下面代碼二中所示,雖然形參通過類型擦除後都爲List類型,可是返回類型不一樣,這是能夠的.
示例1:
/*代碼一:編譯時錯誤*/ public class Erasure{ public void test(int i){ System.out.println("Sting"); } public int test(int i){ System.out.println("Integer"); } }
示例2:
/*代碼二:正確 */ public class Erasure{ public void test(List<String> ls){ System.out.println("Sting"); } public int test(List<Integer> li){ System.out.println("Integer"); } }
2 )泛型類型是被全部調用共享的
全部泛型類的實例都共享同一個運行時類,類型參數信息會在編譯時被擦除.所以考慮以下代碼,雖然ArrayList<String>和ArrayList<Integer>類型參數不一樣,可是他們都共享ArrayList類,因此結果會是true.
示例:
List<String>l1 = new ArrayList<String>(); List<Integer>l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getClass()); //True
3)instanceof
不能對確切的泛型類型使用instanceOf操做.以下面的操做是非法的,編譯時會出錯.
示例:
Collection cs = new ArrayList<String>(); if (cs instanceof Collection<String>){…}// compile error. //若是改爲instanceof Collection<?>則不會出錯.
不能建立一個確切泛型類型的數組.以下面代碼會出錯.
List<String>[] lsa = new ArrayList<String>[10]; //compile error.由於若是能夠這樣, 那麼考慮以下代碼, 會致使運行時錯誤.
示例:
List<String>[] lsa = new ArrayList<String>[10]; // 實際上並不容許這樣建立數組 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所以 只能建立帶通配符的泛型數組 , 如下面例子所示 ,這回能夠經過編譯 ,可是在倒數第二行代碼中必須顯式的轉型才行 ,即使如此 ,最後仍是會拋出類型轉換異常 ,由於存儲在 lsa中的是 List<Integer>類型的對象 ,而不是 List<String>類型 .最後一行代碼是正確的 ,類型匹配 ,不會拋出異常 .
示例:
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 String s = (String) lsa[1].get(0);// run time error, but cast is explicit Integer it = (Integer)lsa[1].get(0); // OK
http://qiemengdao.iteye.com/blog/1525624
20150419
JAVA學習筆記系列
--------------------------------------------
聯繫方式
--------------------------------------------
Weibo: ARESXIONG
E-Mail: aresxdy@gmail.com
------------------------------------------------