天天看點

Java泛型一覽筆錄1、什麼是泛型?2、泛型有什麼用?3、泛型的使用4、有界泛型5、類型擦除6、泛型類型資訊

1、什麼是泛型?

泛型(Generics )是把類型參數化,運用于類、接口、方法中,可以通過執行泛型類型調用 配置設定一個類型,将用配置設定的具體類型替換泛型類型。然後,所配置設定的類型将用于限制容器内使用的值,這樣就無需進行類型轉換,還可以在編譯時提供更強的類型檢查。

2、泛型有什麼用?

泛型主要有兩個好處:

(1)消除顯示的強制類型轉換,提高代碼複用

(2)提供更強的類型檢查,避免運作時的ClassCastException

3、泛型的使用

類型參數(又稱類型變量)用作占位符,訓示在運作時為類配置設定類型。根據需要,可能有一個或多個類型參數,并且可以用于整個類。根據慣例,類型參數是單個大寫字母,該字母用于訓示所定義的參數類型。下面列出每個用例的标準類型參數:

E:元素

K:鍵

N:數字

T:類型

V:值

S、U、V 等:多參數情況中的第 2、3、4 個類型

?  表示不确定的java類型(無限制通配符類型)

4、有界泛型

<? extends T>:是指 “ 上界通配符 (Upper Bounds Wildcards) ”

<? super T>:是指 “ 下界通配符 (Lower Bounds Wildcards) ”

---這裡有個坑

舉個例子,如下,注釋的部分是編譯不通過的。

/**
 * @author Sven Augustus
 */
public class Test {

	static class Species{}
	static class Human extends Species{}
	static class Man extends Human{}
	static class Woman extends Human{}

	public static void main(String[] args) {
		List<Human> list = new ArrayList<Human>();
		list.add(new Man());
		list.add(new Woman());
//		Man o11 = (Man) list.get(0); // 這不能保證轉型成功,也不是泛型的初衷
		Human o12 = list.get(0);

		List<? extends Human> list2 = new ArrayList<Human>();
//		list2.add(new Object()); // 編譯錯誤:這不能寫入元素,類型校驗失敗
//		list2.add(new Species()); // 編譯錯誤:這不能寫入元素,類型校驗失敗
//		list2.add(new Human()); // 編譯錯誤:這不能寫入元素,類型校驗失敗
//		list2.add(new Man()); // 編譯錯誤:這不能寫入元素,類型校驗失敗
//		list2.add(new Woman()); // 編譯錯誤:這不能寫入元素,類型校驗失敗
//		Man o21 = (Man) list2.get(0);// 這不能保證轉型成功,也不是泛型的初衷
		Human o22 = list2.get(0);

		List<? super Human> list3 = new ArrayList<Human>();
//		list3.add(new Object()); // 編譯錯誤:這不能寫入元素,類型校驗失敗
//		list3.add(new Species()); // 編譯錯誤:這不能寫入元素,類型校驗失敗
		list3.add(new Human());
		list3.add(new Man());
		list3.add(new Woman());
//		Man o31 = (Man) list3.get(0); // 這不能保證轉型成功,也不是泛型的初衷
//		Human o32 = list3.get(0); // 編譯錯誤:無法自動轉型為 Number
		Object o33 = list3.get(0);
	}

}           

複制

那麼我們看到

如 List<? extends T> 大家以為元素為 T以及其所有子類的對象 的List。其實不是。元素類型 僅指T的某一個不确定的子類,是單一的一個不确定類,沒有具體哪個類。是以不能插入一個不确定的。

List<? super T> 大家以為元素為 T以及其父類的對象 的List。其實不是,元素類型 僅指T的某一個不确定的父類,是單一的一個不确定類(隻确定是T的父類),沒有具體哪個類。

是以:

不能往List<? extends T>中插入任何類型的對象。唯一可以保證的是,你可以從中讀取到T或者T的子類。

可以往List<? super T>中插入T或者T子類的對象,但不可以插入T父類的對象。可以讀取到Object或者Object子類的對象(你并不知道具體的子類是什麼)。

我們總結一下:

如果頻繁支援讀取資料,不要求寫資料,使用<? extends T>。即生産者 使用 <? extends T>

如果頻繁支援寫入資料,不特别要求讀資料,使用<? super T>。即消費者 使用 <? super T>

如果都需要支援,使用<T>。

5、類型擦除

Java的泛型在編譯期間,所有的泛型資訊都會被擦除掉。

Class c1 = new ArrayList<Integer>().getClass();  
Class c2 = new ArrayList<Long>().getClass();   
System.out.println(c1 == c2);            

複制

這就是 Java 泛型的類型擦除造成的,因為不管是 ArrayList<Integer> 還是 ArrayList<Long>,在編譯時都會被編譯器擦除成了 ArrayList。Java 之是以要避免在建立泛型執行個體時而建立新的類,進而避免運作時的過度消耗。

6、泛型類型資訊

那麼,如果我們确實某些場景,如HTTP或RPC或jackson需要擷取泛型進行序列化反序列化的時候,需要擷取泛型類型資訊。

可以參照如下:

package io.flysium.standard.generic;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 *  擷取運作時的泛型類型資訊
 *
 * @author Sven Augustus
 */
public class Test2 {

	static class ParameterizedTypeReference<T> {
		protected final Type type;

		public ParameterizedTypeReference() {
			Type superClass = this.getClass().getGenericSuperclass();
			//if (superClass instanceof Class) {
	// throw new IllegalArgumentException(
//"Internal error: TypeReference constructed without actual type information");
			//	} else {
				this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
			//}
		}

		public Type getType() {
			return type;
		}
	}

	public static void main(String[] args) {
// System.out.println(new ParameterizedTypeReference<String>().getType());
// java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
// 此處會輸出報錯,是以ParameterizedTypeReference 應不能直接執行個體化,可以考慮加abstract

		System.out.println(new ParameterizedTypeReference<String>() { }.getType());
// ParameterizedTypeReference 的匿名内部類,可以觸發super(),
//即 ParameterizedTypeReference()的構造器邏輯,正常運作
	}

}           

複制

注意一個關鍵點:

可以通過定義類的方式(通常為匿名内部類,因為我們建立這個類隻是為了獲得泛型資訊)在運作時獲得泛型參數。