天天看點

JDK1.5新特性之泛型

------- java學習型技術部落格、期待與您交流 ----------

JDK1.5新特性之泛型

 Jdk 1.5以前的集合類中存在什麼問題?

比如說有這樣的一段代碼:

package com.itheima.day2;

import java.util.ArrayList;

public class GenericTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ArrayList collection = new ArrayList();
		collection.add(1);
		collection.add(1L);
		collection.add("abc");
		int i = (Integer) collection.get(0);
		System.out.println(i);
	}

}
           

我們往一個ArrayList集合中存入多種類型的元素。這時我們想要取出一個類型為整形的元素,那麼需要進行類型強轉

int i = (Integer) collection.get(0);

但是如果我們想取出另一個元素,希望将它當成整形來用

int i = (Integer) collection.get(1);

那麼執行時候就會出現java.lang.ClassCastException

Jdk 1.5的集合類希望你在定義集合時,明确表示你要向集合中裝哪種類型的資料,無法加入指定類型以外的資料,這就是泛型。

加入泛型以後代碼變成下面這樣,我們會發現在編譯時期便會出錯。

package com.itheima.day2;
import java.util.ArrayList;
public class GenericTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ArrayList<Integer> collection = new ArrayList<Integer>();
		collection.add(1);
		collection.add(1L);//編譯出錯
		collection.add("abc")//編譯出錯
		int i = (Integer) collection.get(1);
		System.out.println(i);
	}

}
           

       可以看出,泛型的好處是将運作時期的錯誤轉移至編譯時期,避免了一些安全隐患,同時,還免去了強制轉換的步驟。       

       泛型是提供給javac編譯器使用的,可以限定集合中的輸入類型,讓編譯器擋住源程式中的非法輸入,編譯器編譯帶類型說明的集合時會去除掉“類型”資訊,使程式運作效

率不受影響,對于參數化的泛型類型,getClass()方法的傳回值和原始類型完全一樣。由于編譯生成的位元組碼會去掉泛型的類型資訊,隻要能跳過編譯器,就可以往某個泛型集

合中加入其它類型的資料,例如,用反射得到集合,再調用其add方法即可。

例如下面的程式,雖然我定義集合的時候指定的元素類型是<String>,但是我通過反射得到collection的add方法, 并傳遞了一個整數,結果任然可以運作。原理是我繞過了編譯器,直接得到了collection的位元組碼對象。這也證明了泛型是提供給javac編譯器,且編譯器在翻譯時會自動去除“類型”資訊。

代碼如下:

package com.itheima.day2;
import java.util.ArrayList;
public class GenericTest {

	public static void main(String[] args)throws Exception {
		// TODO Auto-generated method stub
		ArrayList<String> collection = new ArrayList<String>();
		collection.add("abc");
		String i = collection.get(0);
		//System.out.println(i);
		//通過反射的方法調用collection的add方法。
		collection.getClass().getMethod("add", Object.class).invoke(collection, 1);
		System.out.println(collection.get(0));
	}
}
           

了解泛型

ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術語:

    1,整個稱為ArrayList<E>泛型類型

    2,ArrayList<E>中的E稱為類型變量或類型參數 

    3,整個ArrayList<Integer>稱為參數化的類型

    4,ArrayList<Integer>中的Integer稱為類型參數的執行個體或實際類型參數

    5,ArrayList<Integer>中的<>念着typeof

    6,ArrayList稱為原始類型

參數化類型與原始類型的相容性:

    1,參數化類型可以引用一個原始類型的對象,編譯報告警告,例如, Collection<String> c = new Vector();

    2,原始類型可以引用一個參數化類型的對象,編譯報告警告,例如, Collection c = new Vector<String>();

參數化類型不考慮類型參數的繼承關系:

    1,Vector<String> v = new Vector<Object>(); //錯誤!///不寫<Object>沒錯,寫了就是明知故犯 

    2,Vector<Object> v = new Vector<String>(); //也錯誤!

    ps:要麼沒有指定類型,指定了就得一樣,'?'除外。

編譯器不允許建立泛型變量的數組。即在建立數組執行個體時,數組的元素不能使用參數化的類型,例如,下面語句有錯誤:

         Vector<Integer> vectorList[] = new Vector<Integer>[10];

泛型中的'?'通配符

比如說我們需要定義一個方法用于列印不同類型的集合中的元素。

我們寫成下面這樣就會報錯

package com.itheima.day2;
import java.util.ArrayList;
import java.util.Collection;
public class GenericTest {

	public static void main(String[] args)throws Exception {
		// TODO Auto-generated method stub
		ArrayList<String> collection = new ArrayList<String>();
		collection.add("abc");
		String i = collection.get(0);
		//System.out.println(i);
		//通過反射的方法調用collection的add方法。
		collection.getClass().getMethod("add", Object.class).invoke(collection, 1);
		System.out.println(collection.get(0));
		
		printCollection(collection);//這句話會報錯
	}
	public static void printCollection(Collection<Object> colc){
		for(Object obj : colc){
			System.out.println(obj);
		}
	}
}
           

為了解決這個問題,JDK1.5提供了一個‘?’通配符。我們使用‘?’對printCollection()方法進行修改:

public static void printCollection(Collection<?> colc){//參數類型改成了'?'
		for(Object obj : colc){
			System.out.println(obj);
		}
	}
           

這樣便可以順利編譯執行執行了。

總結:使用?通配符可以引用其他各種參數化的類型,?通配符定義的變量主要用作引用,可以調用與參數化無關的方法,不能調用與參數化有關的方法。

驗證,比如我們在上面的Generic類加入一個addCollection的方法,具體代碼如下:

public static void addCollection(Collection<?> collection){//參數類型改成了'?'
		collection.add("haha");//這裡會報錯
		collection.size();//不設計參數,編譯通過
	}
           

結果會發現collection.add("haha");編譯不能通過,因為它用到了一個字元串類型的參數,試想,如果collection中限定的是Integer類型的元素怎麼辦呢,是以肯定不能成功。

泛型中的'?'通配符的擴充(限定)

限定通配符的上邊界:

       正确:Vector<? extends Number> x = new Vector<Integer>();

       錯誤:Vector<? extends Number> x = new Vector<String>();

限定通配符的下邊界:

       正确:Vector<? super Integer> x = new Vector<Number>();

       錯誤:Vector<? super Integer> x = new Vector<Byte>();

ps:Number類中的子類包括8大基本資料類型的封裝類

泛型集合類的綜合應用案例:

需求:通過entrySet取出HashMap集合中的所有元素,要求使用泛型。

package com.itheima.day2;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class GenericTest2 {
	//Map<K,V>
	public static void main(String[] args) {
		HashMap<String,Integer> maps = new HashMap<String,Integer>();
		maps.put("zhangsan", 22);
		maps.put("lisi", 33);
		maps.put("wangwu", 51);
		//泛型限定的類型也有泛型
		Set<Map.Entry<String,Integer>> entrySet = maps.entrySet();
		//通過增強for循環疊代entrySet中的元素,再通過getKey()和getValue()方法取值
		for(Map.Entry<String, Integer> entry : entrySet){
			System.out.println(entry.getKey()+":"+entry.getValue());
		}
	}
}
           

定義泛型方法

    假如我們現在要定義一個交換數組中兩個元素的方法,但是數組中元素的類型還不知道,後者是不确定的。按照傳統方法我們需要定義多個參數不同的方法,但是如果運用泛型,就可以隻定義一個參數不确定的方法,在調用方法的時候傳入不同的參數就行了。

代碼如下:

package com.itheima.day2;

public class GenericTest2 {
	//Map<K,V>
	public static void main(String[] args) {
		//調用swap方法,傳入一個String類型的數組
		swap(new String[]{"abc","xyz"},0,1);

		//注意:下面的代碼會報錯,因為實際類型必須是引用類型
		//swap(new int[]{1,3},0,1);
	}
	//定義一個泛型方法交換數組中的兩個元素,因為數組的類型不定
	public static <E> void swap(E[] a, int i, int j) {
		E t = a[i];
		a[i] = a[j];
		a[j] = t;
	}
}
           

◆ 隻有引用類型才能作為泛型方法的實際參數,swap(new int[3],3,5);語句會報告編譯錯誤。

◆ 除了在應用泛型時可以使用extends限定符,在定義泛型時也可以使用extends限定符,例如,Class.getAnnotation()方法的定義。并且可以用&來指定多個邊界,如:

    <Vextends Serializable & cloneable> void method(){}

◆ 普通方法、構造方法和靜态方法中都可以使用泛型。

◆ 也可以用類型變量表示異常,稱為參數化的異常,可以用于方法的throws清單中,但是不能用于catch子句中。用法如下:

private static <T extends Exception> sayHello() throws T
{
try{
 
}catch(Exception e){
   throw (T)e;
}
}
           

◆在泛型中可以同時有多個類型參數,在定義它們的尖括号中用逗号分,例如:

    public static <K,V> V getValue(K key) { return map.get(key);}

泛型方法的練習:

package com.itheima.day2;

import java.util.Collection;
import java.util.Date;
import java.util.Vector;

public class GenericMethodTest {
	public static void main(String[] args){
		Object obj = "abc";
		String str = autoConvert(obj);
		copy1(new Vector<String>(),new String[10]);
		copy2(new Date[10],new String[10]);
		/*下面這句代碼會報錯,當為Vector指定一個類型為Date後,T也就成了Date,有傳播性*/
		//copy1(new Vector<Date>(),new String[10]);	
	}
	
	//自動将Object類型的對象轉換成其他類型
	private static <T> T autoConvert(Object obj){
		return (T)obj;
	}
	
	//将任意類型的數組中的所有元素填充為相應類型的某個對象
	private static <T> void fillArray(T[] a,T obj){
		for(int i=0;i<a.length;i++){
			a[i] = obj;
		}	
	}
	//列印出任意參數化類型的集合中的所有内容。
	/*
	 * 在這種情況下,前面的通配符方案要比泛型方法更有效,
	 * 當一個類型變量用來表達兩個參數之間或者參數和傳回值之間的關系時,
	 * 即同一個類型變量在方法簽名的兩處被使用,
	 * 或者類型變量在方法體代碼中也被使用而不是僅在簽名的時候使用,
	 * 才需要使用泛型方法。
	 */
	public static <T> void printCollection(Collection<T> colc){
		for(Object obj : colc){
			System.out.println(obj);
		}
	}
	
	//把任意參數類型的集合中的資料安全地複制到相應類型的數組中。
	public static <T> void copy1(Collection<T> dest,T[] src){
		
	}
	
	//把任意參數類型的一個數組中的資料安全地複制到相應類型的另一個數組中。
	public static <T> void copy2(T[] dest,T[] src){
	}
}
           

類型參數的類型推斷 

編譯器判斷範型方法的實際類型參數的過程稱為類型推斷,類型推斷是相對于知覺推斷的,其實作方法是一種非常複雜的過程。

根據調用泛型方法時實際傳遞的參數類型或傳回值的類型來推斷,具體規則如下:

       1)當某個類型變量隻在整個參數清單中的所有參數和傳回值中的一處被應用了,那麼根據調用方法時該處的實際應用類型來确定,這很容易憑着感覺推斷出來,即直接根

            據調用方法時傳遞的參數類型或傳回值來決定泛型參數的類型,例如:

             swap(new String[3],3,4)  --->  static <E> void swap(E[] a, int i, int j)

       2)當某個類型變量在整個參數清單中的所有參數和傳回值中的多處被應用了,如果調用方法時這多處的實際應用類型都對應同一種類型來确定,這很容易憑着感覺推斷出

            來,例如:

             add(3,5) --->static <T> T add(T a, T b)

       3) 當某個類型變量在整個參數清單中的所有參數和傳回值中的多處被應用了,如果調用方法時這多處的實際應用類型對應到了不同的類型,且沒有使用傳回值,這時候取

           多個參數中的最大交集類型,例如,下面語句實際對應的類型就是Number了,編譯沒問題,隻是運作時出問題:

             fill(new Integer[3],3.5f) ---> static <T> void fill(T[] a, T v)

       4) 當某個類型變量在整個參數清單中的所有參數和傳回值中的多處被應用了,如果調用方法時這多處的實際應用類型對應到了不同的類型, 并且使用傳回值,這時候優先

           考慮傳回值的類型,例如,下面語句實際對應的類型就是Integer了,編譯将報告錯誤,将變量x的類型改為float,對比eclipse報告的錯誤提示,接着再将變量x類型改為

           Number,則沒有了錯誤:

            int x =(3,3.5f) ----> static <T> T add(T a, T b) 

       5) 參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型為Object,編譯沒有問題,而第二種情況則根據參數化的Vector類執行個體将類型變量直接确定為String

           類型,編譯将出現問題:

           copy(new Integer[5],new String[5]) ---->  static <T> void copy(T[] a,T[]  b);

           copy(new Vector<String>(), new Integer[5]) ----> static <T> void copy(Collection<T> a , T[] b);

定義泛型類型

如果類的執行個體對象中的多處都要用到同一個泛型參數,即這些地方引用的泛型類型要保持同一個實際類型時,這時候就要采用泛型類型的方式進行定義,也就是類級别的泛型,

文法格式如下:

public class GenericDao<T> {
		private T field1;
		public void save(T obj){}
		public T getById(int id){}
	}
           

類級别的泛型是根據引用該類名時指定的類型資訊來參數化類型變量的,例如,如下兩種方式都可以:

方式1:GenericDao<String> dao = null;

方式2:new genericDao<String>();

注意:

◆ 在對泛型類型進行參數化時,類型參數的執行個體必須是引用類型,不能是基本類型。

◆當一個變量被聲明為泛型時,隻能被執行個體變量、方法和内部類調用,而不能被靜态變量和靜态方法調用。因為靜态成員是被所有參數化的類所共享的,是以靜态成員不應該

    有類級别的類型參數。

在JavaEE中經常需要對資料庫進行增删查改,經常會定義一個類似于下面這樣的類

package com.itheima.day2;

import java.util.Set;

public class GenericDao<E> {

	//增
	public void add(E x){
		
	}
	//查
	public E findById(int id){
		return null;
	}
	//删
	public void delete(E obj){
		
	}
	//删(重載)
	public void delete(int id){
		
	}
	//改
	public void update(E obj){
		
	}
	//根據條件查找
	public Set<E> findByConditions(String where){
		return null;
	}
	//靜态方法不應該有類級别的類型參數(對象建立之前沒法知道對象的類型)
	/*
	 public static void update2(E obj){	
	}*/
	//如果靜态方法也要用到泛型需要自己在方法上定義
	public static <E> void update2(E obj){
		
	}
}
           

記住:如果一個類中的多個方法要使用到泛型,應該是用類級别的泛型!

通過反射獲得泛型的參數化類型

假如我們有一個需求,在我們定義了一個帶參數的Vcteor<Date> v1集合後,我們需要擷取其參數的類型。

由于泛型是針對編譯器存在的,編譯成的位元組碼檔案中的類型已經被擦出了,是以如果通過v1.getClass()方法是不可能擷取其類型的。

但是,如果一個方法将Vector<Date>對象作為了參數,這個方法的某些方法可以獲得其參數的類型,然後這個類型的某些方法也可以獲得其具體類型。

代碼如下:

package com.itheima.day2;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Vector;

public class GenericalReflection  {
	  private Vector<Date> v1 = new Vector<Date>();
	  public void applyVector(Vector<Date> v1) {
	  }
	public static void main(String[] args)throws Exception {
		//首先通過類的位元組碼檔案擷取類中以Vector為參數的方法applyVector
		Method applyMethod = GenericalReflection.class.getMethod("applyVector", Vector.class);
		//根據方法對象的getGenericParameterTypes方法擷取applyVector方法的參數類型(傳回的是Class類型的數組)
		Type[] type = applyMethod.getGenericParameterTypes();
		//System.out.println(type[0]);
		//将擷取到的參數類型類型數組中的第一個對象,轉換為ParameterizedType(參數化類型)類型
		ParameterizedType typeP = (ParameterizedType)type[0];
		//通過getActualTypeArguments方法擷取其實際類型(傳回的是數組)
		System.out.println(typeP.getActualTypeArguments()[0]);
	}
}
           

ps:雖然掌握了泛型一些基礎的用法, 但是對泛型方法,泛型類,接口等不太熟悉。另外個人覺得,雖然這個機制翻譯過來叫“泛型”,但是不是不太合适,“泛型”的出現是為了限制我們存入亂七八糟的對象,那麼這樣一限制不就沒那麼廣泛了嗎?以上觀點純屬胡言亂語。在集合類中經常需要使用到泛型,為了學好泛型,還得補下集合,相輔相成。

------- java學習型技術部落格、期待與您交流 ----------

繼續閱讀