java泛型

本文總結java中泛型概念及其用法。java泛型技術在集合框架中應用廣泛,學習及總結時梳理了主要內容進行描述,一些零散的泛型使用注意事項體現在代碼。

泛型
jdk1.5版本開始出現的新特性,用於解決安全問題,是一個類型安全機制。
jdk1.5中,還可以按原來的方式將各種不同類型的數據裝到一個集合中,但編譯器會報unchecked警告。

使用泛型的好處
1. 將運行時期出現的ClassCastException問題,轉換到了編譯時期,方便程序員解決問題,讓運行期間問題減少,安全。
2. 避免了類型強制轉換麻煩。
沒有泛型之前,函數/方法擴展兼容性時一般將形參定義爲Object類型,以接收作一類型,而Object類型參數在使用時一般需進行強制類型轉換。

泛型格式
通過<>定義要操作的引用數據類型,只要見到<>,就是定義泛型,<>就是用來接收類型的。

泛型常用術語
ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術語:
1. 整個稱爲ArrayList<E>泛型類型

2. ArrayList<E>中E稱爲類型變量或類型參數
3. 整個ArrayList<Integer>稱爲參數化的類型
4. ArrayList<Integer>中的Integer稱爲類型參數的實例或實際參數類型
5. ArrayList<Integer>中的<>念typeof
6. ArrayList稱爲原始類型

定義帶泛型的變量時,有一些注意事項,演示在下面在代碼行和註釋中:

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

public class GenericDemo1 {
	public static void main(String[] args) {
		//參數化類型可以引用一個原始類型的對象,編譯報警告
		Collection<String> collection3=new Vector();
		//原始類型可以引用一個參數化類型對象,編譯報警告
		Collection collection4=new Vector<String>();
		Vector<Integer> v1=new Vector<Integer>();
		/*參數化類型不考慮類型參數的繼承關係,下面2句編譯失敗
		 Vector<Stirng> v2=new Vector<Object>();
		 Vector<Object> v3=new Vecotr<String>();
		 Vector<Object> v3=v1;*/

		/*創建數組實例時,數組的元素不能使用參數化的類型,下面語句中vectorLIst是一個數組,數組中的元素不能是Vector<Integer>類型的對象
		Vector<Integer> vectorList[]=new Vector<Integer>[10];*/	
	}
}

泛型在集合框架中很常見,使用集合時,將集合中要存儲的數據類型作爲參數傳遞到<>中即可。

import java.util.*;

public class GenericMapDemo {

	public static void main(String[] args) {
		HashMap<String,Integer> maps=new HashMap<String,Integer>();
		maps.put("liso", 23);
		maps.put("wangwu", 25);
		maps.put("zhaoliu",40);
		Set<Map.Entry<String, Integer>> entrySet=maps.entrySet();
		for(Map.Entry<String, Integer> entry:entrySet){
			System.out.println(entry.getKey()+" : "+entry.getValue());
		}
		System.out.println("===========================");
		//遍歷集合的另一種方式
		Set<String> kSet=maps.keySet();
		Iterator<String> it=kSet.iterator();
		while(it.hasNext()){
			String str=it.next();
			System.out.println(str+" : "+maps.get(str));
		}
	}
}

泛型除了可以用在集合或數組變量上,還可以用在類和方法上。
泛型方法
爲了讓類中的不同方法可以操作不同類型,且類型還沒確定,可以將泛型定義在方法上。
泛型定義在方法上時,放在返回值之前,修飾符之後。
普通方法、構造方法和靜態方法中都可以使用泛型。

泛型方法的使用方法及一些注意事項見下面的代碼:

import java.io.Serializable;
class GenericMethod {
	public <T> void show(T t){
		System.out.println("show: "+t);
	}
	public <E> void print(E e){
		System.out.println("print: "+e);
	}
	public <T> T add(T x,T y){
		//return (T)(x+y);編譯不通過
		return null;
	}
	//給定任一引用類型的數組,交換其中的元素
	public <T> void swap(T[] a,int i,int j){
		T tmp=a[i];
		a[i]=a[j];
		a[j]=tmp;
	}
	//定義泛型方法時也可以使用extends、super限定符來限定邊界
	public <T extends Number> T addNumber(T a,T b){
		return null;
	}
	//可以限定多個邊界
	public <V extends Serializable & Cloneable> void method(){
		
	}
	//可以用類型變量表示異常,稱爲參數化的異常,可以用於方法的throws列表中,但不能用於catch子句中
	private static <T extends Exception> void sayHello() throws T
	{
		try{
			
		}
		catch(Exception e){//不能寫成catch(T e)
			throw (T)e;
		}
	}
	//泛型中可以同時有多個類型參數,在定義它們的尖括號中用,分開
	public static <K,V> V getVal(K key){
		return null;
	}	
}
public class GenericMethodDemo{
	public static void main(String[] args){
		GenericMethod gm=new GenericMethod();
		//調用泛型方法時可以隨意傳入任意類型的參數
		gm.print("haha");
		gm.print(4);
		gm.show(56);
		
		//泛型類型推斷
		Number n=gm.add(3.5, 5);//基本數據類型自動裝箱成引用類型數據,
		//Double d=gm.add(3.5, 5);//編譯不通過,此時的實際類型參數必須是Double和Integer的共同父類,java編譯器會進行類型推斷來確定和檢查類型參數
	    Object obj=gm.add(8, "abd");//Integer和String類型的交集類型是Oject
	    
	    gm.swap(new String[]{"abc", "bdg", "iesdkl"},1,2);
	    //gm.swap(new int[]{2, 4, 5,9},3,1);編譯不通過,實際類型參數不能是基本數據類型
	    
	    //addNumber()只能接收Number類及其子類類型對象
	    gm.addNumber(4, 6);
	    gm.addNumber(6.7,9);	    
	    //gm.addNumber("abd", "u");編譯不通過
	}
}

泛型類
當類中要操作的引用數據類型(不能是基本數據類型)不確定時,早期定義Object來完成擴展,現在定義泛型來完成擴展。
泛型類定義的泛型,在整個類中有效。泛型類對象中的具體類型確定後,對象就只能操作這一個類型。
注:類中只有一個方法需要使用泛型時,應該使用類級別的泛型,而不是方法級別的泛型。

泛型類的使用方法及一些注意事項見下面的泛型類示例:

//泛型類
class GenericDao<T> {
	private T field;
	public void save(T obj){
		this.field=obj;
	}
	public T getField(){
		return field;
	}
	/*編譯不通過,靜態方法不可以訪問類上定義的泛型,因爲靜態方法先於泛型對象加載
	public static T update(T obj){
		
	}*/
	//靜態方法操作的引用數據類型不確定時,可以將泛型定義在方法上
	public static <T> T update(T obj){
		return obj;
	}
	//泛型類中還可以定義 泛型方法,泛型方法使用的類型與泛型類中的可以不相同
	public <E> void display(E e){
		System.out.println(e);
	}
}
public class GenericDaoTest{
	public static void main(String[] args){
		GenericDao<Integer> g=new GenericDao<Integer>();
		//編譯不通過,泛型類的類型參數化時,類型參數的實例必須是引用類型,不能是基本類型
		//GenericDao<int> gg=new GenericDao<int>();
		g.save(8);
		System.out.println(g.getField());
		g.display("hehe");
		System.out.println(GenericDao.update("haha"));
		//編譯不通過
		//g.save("aaa");
	}
}

泛型的通配符
類型參數?表示可接收任一泛型
? extends E:可以接收E類型或者E類型的子類型
? super E:可以接E類型或E類型的父類型

泛型通配符使用方法及注意事項演示在下面的代碼中:

import java.util.*;

public class GenericDemo4 {

	public static void main(String[] args) {
		//泛型中的通配符基本用法
		//Integer是Number類的子類
		Vector<? extends Number> v4=new Vector<Integer>();
		//Vector<? extends Number> v5=new Vector<Object>();//編譯不通過
		Vector<? super Integer> v6=new Vector<Number>();
		//Vector<? super Integer> v7=new Vector<Byte>();編譯不通過
		Class<?> y;
		Class<String> x=String.class;
		y=x;
		//x=y;編譯不通過,任一個類型泛型對象不能賦給指定類型泛型對象
	}
	public static void printCollection(Collection<?> collection){
		//方法體中不能調用與參數化有關的方法,因爲不知道自己未來匹配的類型是不是String
		//collection.add("afb");
		collection=new ArrayList<String>();//沒錯,可以整體指定泛型參數實例。
		//可以調用與參數化無關的方法
		System.out.println(collection.size());
	}
}
java泛型原理

java中的泛型類型(或者泛型)類似於C++中的模板,但這種類似性僅限於表面,java語言中的泛型基本上完全是在編譯器中實現,用於給編譯器執行類型檢查和類型推斷,然後生成普通的非泛型的字節碼,這種實現技術稱爲擦除(erasure)(編譯器使用泛型類型信息保證類型安全,然後在生成字節之前將其清除)。這是因爲擴展虛擬機指令集來支持泛型被認爲是無法接受的,這會爲java廠商升級其jvm造成難以逾越的障礙。所以java的泛型採用了完全可以在編譯器中實現的擦除方法。

泛型是提供給java編譯器使用的,讓編譯器擋住源程序中的非法輸入;已參數化的泛型類型,getClass方法的返回值與原始類型完全一樣,編譯器編譯完成後,生成的字節碼中會去掉泛型的類型信息,使程序運行效率不受影響。

只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,例如,用反射得到集合,再調用add()方法即可。

利用反射還可以得到泛型的實際類型參數:

import java.lang.reflect.*;
import java.util.*;

public class GenericDemo3 {
	public static void main(String[] args) throws Exception
	{
		ArrayList<String> collection1=new ArrayList<String>();
		ArrayList<Integer> collection2=new ArrayList<Integer>();
		System.out.println(collection1.getClass()==collection2.getClass());//true
		//利用反射技術向實際類型參數爲Integer的泛型類型對象中添加String字符串
		collection2.getClass().getMethod("add", Object.class).invoke(collection2, "abc");
		System.out.println(collection2.get(0));//打印集合的第1個元素"abc"
		
		//利用反射技術獲取泛型的實際類型參數
		Method applyMethod=GenericDemo3.class.getMethod("applyVector", Vector.class);
		//獲取Method對象的所有形參類型,該方法在jdk1.5版本纔有
		Type[] types=applyMethod.getGenericParameterTypes();
		//獲取第1個形參類型,ParameterizedType是一個接口,表示參數化類型,如 Collection<String>
		ParameterizedType pType=(ParameterizedType)types[0];
		//獲取參數的類型
		System.out.println(pType.getRawType());
		//返回泛型的實際類型參數
		System.out.println(pType.getActualTypeArguments()[0]);
	}
	public static void applyVector(Vector<Date> v1){
		
	}	
}
/*運行結果:
 true
abc
class java.util.Vector
class java.util.Date
*/





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章