Java中泛型的定義,用法與類型通配符的使用

一 概述

Java泛型是JDK1.5中引入的新特性,本質是參數化類型,意思是允許在定義類、接口、方法時使用類型形參,當使用時指定具體類型,所有使用該泛型參數的地方都被統一化,保證類型一致。如果未指定具體類型,默認是Object類型。也就是所操作的數據類型被指定一個參數,這種參數可以在類,接口和方法的創建中,分別爲泛型類,泛型接口,泛型方法。此外,集合體系中的所有類都增加了泛型,泛型也主要用在集合。

二 泛型的限制和規則

  • 泛型的類型參數只能是引用類型,不能是值類型,也可以理解爲只能是包裝類型,不能是基本數據類型。
  • 泛型的類型參數可以是多個。
  • 泛型類不是真正存在的類,不能夠通過instanceof運算符運算。
  • 泛型類的類型參數不能在靜態中聲明。
  • 如果定義了泛型,不指定具體的數據類型,泛型默認指定爲Object類型。
  • 泛型使用?作爲類型通配符,表示未知類型,可以匹配任何類型,但由於是未知,所以無法添加元素。
  • 類型通配符上限<? extends T>,?代表的是T的子類或者是T本身。常用於泛型方法,避免類型轉換。
  • 類型通配符下限<? super T>,?代表的是T的父類或者是T本身。
  • 通配符可以實現限制類,接口和方法中定義的泛型參數的上限和下限。

三 泛型的定義

泛型類:public class Demo<T> { } T爲未知類型。

泛型接口:public interface ImplDemo<T> { } 由於接口是一個特殊的類,所以同泛型類。

泛型方法:public <T> void method(T data) { int a; } 
         public <T> T method2(T t) { return t;}

四 泛型的優點

  1. 泛型對於非靜態的參數會在編譯的時候確定類型,且保證類型安全,從而避免類型轉換異常。
  2. 泛型可以避免類型之間的強制轉換。
  3. 使用泛型可以提高代碼的複用,從而使得代碼更加具有通用性。

五 泛型代碼實例

接下來,使用代碼來演示泛型的用途,建議使用目錄查看具體內容。

集合類演示泛型:

//未指定泛型
TreeSet ts = new TreeSet();
ts.add(10);
ts.add(25);
ts.add("30");
System.out.println(ts);//運行時報錯,類型轉換異常
		
//mode 2
TreeSet<Integer> ts2 = new TreeSet<>();
ts2.add(10);
ts2.add(25);
ts2.add("30"); //編譯器提示報錯,無法添加非Integer類型

未使用泛型時,可以添加任意元素,因爲TreeSet會比較每一個元素,所以運行時會引發類型轉換異常。使用泛型後,只能添加同一個類型,所以不會出錯。

定義泛型類

public class Person<T> {
	private T name;//類型是未知的
	
	public Person(T name) {
		this.name = name;
	}	
	
	public T getName() {
		return name;
	}
	
	public void sexName(T name) {
		this.name = name;
	}
}

在上面實例中,Person類定義成泛型類,成員變量name的類型指定爲T,表示未知類型。實例化該類對象後,可以看到name的類型是Object,表示可以接收任何類型。

Person p = new Person(10);	//new Person(Object name)

加上泛型後

//使用泛型兩種方式

Person<String> ps = new Person<String>(); //new Person<String>(String name)
Person<String> ps = new Person<>();//new Person<>(T name)

第一種,會直接確定參數類型是什麼,而第二種的參數類型是T ,但如果加入的非String類型,編譯器會檢查並報錯。兩者區別不大。在JDK1.7之後使用第二種,會自動檢查泛型,可以省略後部分<>的泛型參數,建議JDK1.7版本或更高版本使用第二種

定義泛型接口

interface A<T>{
	void display(T value); 
	T getValue(T v);
}
//未對泛型接口指定具體類型
public class Person implements A{

	@Override
	public void display(Object obj) {
		System.out.println();
	}

	@Override
	public Object getValue(Object v) {
		return null;
	}
}

如果我們定義了泛型,不指定具體類型,默認就是Object類型。當我們爲泛型接口指定具體類型後,代碼如下:

//泛型接口
interface A<T>{
	void display(T value); 
	T getValue(T v);
}

//爲泛型接口指定具體類型
public class Person implements A<String>{
	@Override
	public void display(String value) {
	}
	@Override
	public String getValue(String v) {
		return null;
	}
}

泛型接口指定具體類型後,所有使用了該泛型參數的地方都被統一化。其實泛型接口和泛型類是一樣的寫法。

定義泛型方法

先使用常規方法進行對比。

public static void main(String[] args) {
	int[] arr = new int[] {1, 8, 15, 6, 3};
	double[] douArr = {10.5, 25.1, 4.9, 1.8};
	String[] strArr = {"我","是","字","符","串"};
	forArr(strArr);	
}
	
//遍歷數組的重載方法,支持int和double類型	
	public static void forArr(int[] arr) {
		for(int i=0; i<arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
	//重載了
	public static void forArr(double[] arr) {
		for(double d : arr) {
			System.out.println(d);
		}
	}

如上所示,如果想遍歷Stirng類型數組,那就還要再次重載代碼,如果是八種類型都有,代碼量非常龐大。使用泛型方法全部通用,代碼如下:

public static void main(String[] args) {
	Integer[] arr =  {1, 8, 15, 6, 3};
	Double[] douArr = {10.5, 25.1, 4.9, 1.8};
	String[] strArr = {"我","是","字","符","串"};
		
	forArrGenric(strArr);
	}
//泛型方法
public static <T> void forArrGenric(T[] arr) {
	for(int i=0; i < arr.length; i++) {
		System.out.println(arr[i]);
	}
	}

只需定義一個泛型方法,根據運行時傳入的參數類型,動態地獲取類型,就能做到遍歷所有類型數組。但需要注意,泛型的類型參數只能是引用類型,值類型無法在泛型中使用,所以上面的數組都改成了引用類型。值類型需要使用對應的包裝類類型。

使用類型通配符

使用之前,先使用常規方式來進行比較。

public static void main(String[] args) {
	HashSet hs = new HashSet();
	hs.add("A");
	hs.add("QQ");
	hs.add("Alipay");

	new Test2().test2(hs);
	}	
//普通遍歷Set集合,Set是泛型接口,沒指定具體泛型參數會引起警告
public void test(Set s) {
	for(Object o : s)
		System.out.println(o);
	}	
//增加泛型參數,參數類型是Set<Object>
public void test2(Set<Object> s) {
	for(Object o : s)
		System.out.println(o);
}

方法參數的Set集合使用了泛型參數<Object>,方便將參數類型轉換成Object,看起來沒什麼錯。當傳入一個帶泛型參數的集合時,會出現編譯錯誤。代碼如下:

public static void main(String[] args) {
	HashSet<String> hs = new HashSet();
	hs.add("A");
	hs.add("QQ");
	hs.add("Alipay");
	new Test2().test2(hs); //error
	}	
//增加泛型參數,參數類型是Set<Object>
public void test2(Set<Object> s) {
	for(Object o : s)
		System.out.println(o);
}

因爲泛型類不是真正存在的類,所以Set<String>和Set<Object>不存在關係,自然無法作爲參數傳入進去。這時我們就可以使用類型通配符,如下:

//使用類型通配符作爲類型參數
public void test2(Set<?> s) {
	for(Object o : s)
		System.out.println(o);
}

Set<?>表示可以匹配任意的泛型Set。雖然可以使用各種泛型Set了。但弊端就是類型未知,所以無法添加元素。還有範圍過於廣泛,所以這時可以考慮限制的類型通配符。

限制的類型通配符

上面代碼只要是泛型Set都允許被遍歷,如果只想類型通配符表示一個類和其子類本身呢?設置類型通配符上限,代碼如下:

public class Test2 {
	public static void main(String[] args) {
		ArrayList<Test2> ar = new ArrayList<>();
		List<Test3> lt = new ArrayList<>();
		List<String> lStr = new ArrayList<>();
		demo(ar);
		demo(lt);
		demo(lStr); //error
	}	
	//限制的類型通配符
	public static void demo(List<? extends Test2> t) {
		for(int i = 0; i < t.size(); i++) {
			System.out.println(t.get(i));
		}
	}
}
class Test3 extends Test2{}//子類

<? extends T>:表示類型是T本身或者是T類型的子類類型,稱作類型通配符的上限制。

<? super T>:表示類型是T類型本身或者是T類型的父類類型,稱作類型通配符的下限。使用方式同上。

泛型爲何不能應用於靜態申明的實例解析

先給一個例子,在靜態變量中和靜態代碼塊中使用泛型。

public class Test<T> {
	public static T name;  //error
	public T sex ;
	
	static {
		T ab; //error
	}
}

報出異常:不能使一個靜態引用指向一個非靜態的類型 T。靜態和非靜態之分就在於靜態是編譯時類型,動態是運行時類型。T代表未知類型,如果可以用於靜態申明,因爲是未知類型,系統沒法指定初始值,手動賦值也不行,因爲不知道啥類型,只有運行時纔可以指定。而泛型存在的意義就是爲了動態指定具體類型,增強靈活性和通用性,所以用於靜態聲明違背了使用原則。爲什麼實例變量和實例方法可以使用呢?因爲當你使用實例變量或者方法時,就說明對象存在了,即代表泛型參數也指定了。未指定具體類型默認是Object類型。

爲什麼靜態方法中可以定義泛型方法呢?

先給三個實例,我們來慢慢分析。

public class Test<T> {
	public static void main(String[] args) {
		
	}
	
	//泛型方法
	public T demo1(T t) {
		return t;
	}
	
	//靜態方法使用泛型參數
        //public static T demo2(T t) { return t;}
				
	//定義泛型靜態方法
	public static <T> void demo3(T t) {
		System.out.println(w);
	}  
}

首先,要明確一點,泛型作用是確定具體類型。先看一個泛型方法,使用了泛型參數T作爲返回值,當使用對象時來調用該方法時,T類型會變成具體類型。第二個泛型方法是靜態的,使用了T作爲返回值和方法參數類型,但是靜態方法是屬於類的,類直接調用的話,T類型無法指定具體類型,那麼該方法就沒有意義。所以直接報錯。第三個也是靜態方法,但是該靜態方法是自定義一個泛型參數,並非使用類型參數。所以當傳入一個具體類型時,該靜態方法的<T>就是具體類型了。兩者靜態方法的區別就是一個是靜態方法使用泛型參數,一個是定義泛型靜態方法。

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