Javaの泛型

一、泛型的概念

我們經常需要對多個類型的數據做相同的操作,但爲各個類型分別編寫方法和類是十分低效的。爲此,JDK-5引入了泛型
泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型
泛型的實質是類型參數化,將數據類型作爲參數進行傳遞。
類型參數必須使用引用型類型

二、泛型

1.泛型方法

泛型方法可以在調用時接收不同類型的參數、規定不同類型的返回值
一個泛型方法定義如下:

<類型參數聲明>返回類型 方法名(形參表){
	//方法定義
}

下面是簡單的舉例,並使用兩種方法調用泛型方法:

public class Demo {
	public static void main(String[] args) {
		Integer i = 123;
		Demo.<Integer>print(i);		//給定類型參數調用,該方法必須配合使用句點表示法,無法自動推斷時使用
		print(i);					//Java8新增類型推斷,一般採用該方法
	}
	
	public static <T> void print(T t) {
		System.out.println(t);
	}
}

如果想要的類型參數在某一個特定範圍內(通常是繼承層次中的範圍),可以通過使用extends關鍵字。這個關鍵詞規定了類型實參必須繼承了某個類,或者實現了某個接口

//如沒有extends,編譯無法通過,因爲不知道T是否定義了compareTo方法
public <T extends Comparable<T>> boolean smallerThan(T o1,T o2) {	
	if(o1.compareTo(o2) < 0)	
		return true;
	else
		return false;
}

泛型方法也可以使用可變參數,如:

public <T> void print(T...args) {
	for(T t : args)
		System.out.println(t);
}

2.泛型類

泛型類可以對不同類型的數據開放相同的接口。最典型的就是各種容器類。
一個泛型類定義如下:

class 類名<類型參數聲明>{
	//實例/局部變量和實例方法可以使用類聲明的類型參數
}

下面是一個簡單的舉例及兩種使用方法:

class Test<T>{
	T value;
	public void set(T value) {
		this.value = value;
	}
	
	public T get() {
		return value;
	}
}

public class Demo {
	public static void main(String[] args) {
		//方法一:給定類型實參,實例對象只能接受給定類型的數據
		Test<Integer> test1 = new Test<Integer>();
		test1.set(6);
		//方法二:不給定類型實參,實例對象可以接受任何類型的數據
		Test test2 = new Test();
		test2.set(1);
		test2.set(2.0);
		test2.set("3");
	}
}

泛型方法每次調用時都會重新壓棧,重新推斷類型,因此是安全的。在可以自動推斷的情況下應儘量使用。
泛型類的實例一旦初始化,在其作用域內就始終存在。中途改變其類型參數是不安全的,應使用給定實參的方式。

泛型類中的泛型方法
並不是所有泛型類中的方法都是泛型方法,如:

class Test<T>{
	T value;
	public void set(T value) {	//不是一個泛型方法,只是使用了類聲明的類型形參
		this.value = value;
	}
	
	public T get() {		//同上,不是一個泛型方法
		return value;
	}
	
	public <E> void print(E arg){		//這是一個泛型方法,他有單獨的類型參數聲明,這個方法中T和E均可使用
		System.out.print(arg.toString());
	}
	
	public <T> void println(T arg) {	//這也是一個泛型方法,但它將類給定的類型參數T隱藏(屏蔽)了,這裏的T是自身的類型形參
		System.out.println(arg.toString());
	}
	//這兩個方法主要是爲了說明泛型類中的泛型方法,明確概念,兩個函數本身不具應用意義
}

靜態方法與泛型
靜態方法無法訪問實例成員,而類型參數也是實例成員的一種,因此靜態方法不可使用類的類型參數
例如在上面的類中添加如下的靜態方法是不能通過編譯的:

public static void print2(T arg){	//Error:不能對非靜態類型T進行靜態引用
	System.out.println(arg);
}

應當爲靜態方法給出一個自身的類型參數聲明,修改如下:

public static <T> void print2(T arg) {
	System.out.println(arg);
}

3.泛型接口

泛型接口的定義與泛型類基本相同,常被用在各種類的生產器,例如比較器

interface Print <T>{			//輸出器
	public void print(T o);
}

使用類實現泛型接口時,需要爲泛型接口傳遞類型參數。常見的是泛型類爲泛型接口傳遞形參:

interface Print <T>{
	public void print(T o);
}

class Test<T> implements Print<T>{
	@Override
	public void print(T o) {
		System.out.println(o);
	}
}

4.類型通配符

當同一個泛型類被不同的類型參數實例化後,就變成了不同的版本。版本不同的實例是不兼容的,哪怕是基類與子類之間。

public class Demo {
	public static void main(String[] args) {
		test(new Test<Base>());			//無錯誤
		test(new Test<Heir>());			//Error:方法 test(Test<Base>)對於參數(Test<Heir>)不適用
	}
	
	public static void test(Test<Base> arg) {	//規定僅接受Test<Base>版本,<>裏必須給定類型,否則報錯
		System.out.println(arg);
	}
}

class Base{}
class Heir extends Base{}
class Test<T>{}

爲了解決這一問題,我們使用 ? 作爲類型通配符,它可以指代所有的類型。將上面的test方法作如下修改,就可以通過編譯。

//public static void test(Test<Base> arg)
public static void test(Test<?> arg) {
	System.out.println(arg);
}

5.泛型的上下邊界

使用泛型時,我們可以爲接受的類型參數界定上下邊界
規定傳入類型必須是某個類型的子類/實現某個接口(上界),或必須是某個類型的父類(下界)。
extends 關鍵字用於規定上界,規定上界可以允許泛型中使用父類的實例成員,並允許多態

public class Demo {
	public static void main(String[] args) {
		test(new Test<Base>(new Base()));	//class learning_test.Base
		test(new Test<Heir>(new Heir()));	//class learning_test.Heir
	}
	
	public static void test(Test<? extends Base> arg) {
		arg.print();
	}
}

class Base{}
class Heir extends Base{}
class Test<T>{
	T value;
	public Test (T value) {
		this.value = value;
	}
	public void print() {
		System.out.println(value.getClass().toString());
	}
}

super關鍵字用於規定下界,即只接收給定類型的父類類型。將上面的test形參該位Test<? super Heir>,main方法中會報錯。

test(new Test<Base>(new Base()));	//無措
test(new Test<Heir>(new Heir()));	//Error:方法 test(Test<? super Base>)對於參數(Test<Heir>)不適用

測試時發現,extends對於任意的泛型都可以使用,但super只能與類型通配符一起使用。

public static <T extends Base> void print1(T o){}	//無錯誤
public static <T super Base> void print2(T o){}		//Error:標記“super”上有語法錯誤,應爲,
//第二行的錯誤報告看上去很奇怪,可能是將super識別爲父類關鍵字super使用了
public static void print3(List<? extends Base> list) {}	//無錯誤
public static void print4(List<? super Base> list) {}	//無錯誤
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章