banner
jzman

jzman

Coding、思考、自觉。
github

Java系列之泛型

自從 JDK 1.5 提供了泛型概念,泛型使得開發者可以定義較為安全的類型,不至於強制類型轉化時出現類型轉化異常,在沒有反省之前,可以通過 Object 來完成不同類型數據之間的操作,但是強制類型轉換(向下轉型)在不確定具體類型的情況下會出錯,泛型機制的引入就是解決數據類型不明確 的問題。

  1. 定義泛型類
  2. 定義泛型接口
  3. 定義泛型方法
  4. 泛型繼承
  5. 泛型擦除
  6. 泛型的高級用法
  7. 泛型作用

定義泛型類#

定義一個泛型類,語法如下:

//定義泛型類
class 類名<T>{
    
}

其中,T 表示一個類型的名稱,T 可以表示成其他名稱,一般習慣寫成 T,<> 裡面的類型可以有多個,中間以逗號隔開,下面是一個泛型類,具體如下:

/**
 * 定義泛型類
 * @author jzman
 * @param <T>
 */
public class GenercityClass<T1,T2> {
	
	private T1 score;
	private T2 desc;
	
	public GenercityClass() {}
	
	public GenercityClass(T1 score, T2 desc) {
		this.score = score;
		this.desc = desc;
	}
	public T1 getScore() {
		return score;
	}
	public void setScore(T1 score) {
		this.score = score;
	}
	public T2 getDesc() {
		return desc;
	}
	public void setDesc(T2 desc) {
		this.desc = desc;
	}
	
	public static void main(String[] args) {
		//使用時指定具體類型,具體類型只能是引用類型,不能是基本類型,如 int
		GenercityClass<Integer, String> genercity = new GenercityClass<>(90,"A");
		int score = genercity.getScore();
		String desc = genercity.getDesc();
		System.out.println("score="+score+",desc="+desc);
	}
}

顯然,使用泛型定義的類可以在使用時根據不同的需求指定 <T1,T2> 所代表的真實類型,這樣就不會有類型轉換操作,將不會出現 ClassCastException 異常,編譯器會提前檢查類型是否匹配,下面這樣會出錯,具體如下:

GenercityClass<Integer, String> genercityClass = new GenercityClass<>();
//符合指定的具體類型
genercityClass.setScore(80);
//不能將String的值賦值給Integer類型的變量
genercityClass.setScore("A");

定義泛型接口#

定義泛型接口,語法具體如下:

//定義泛型接口
interface 接口名<T>{
    方法返回值類型 方法名<T t>;
}

下面是一個泛型接口的定義,具體如下:

/**
 * 泛型接口:只能在方法中使用指定泛型
 * @author jzman
 */
public interface GenercityInterface<T> {
	//泛型不能在靜態屬性前面使用
//	T a;
	//方法中使用泛型
	void start(T t);
}

在接口中使用泛型,只能在接口的方法中使用泛型,不能在接口的屬性上使用泛型,因為 Java 接口中的屬性是用 public static final 修飾的,而泛型所代表的具體類型是在使用時確定的,編譯時還不知道泛型所表示的具體類型。

定義泛型方法#

定義泛型方法,語法具體如下:

//定義泛型方法
修飾符 <T> 返回值類型 方法名{
    
}

創建一個類,具體如下:

package com.manu.genericity;

public class PersonBean {
	private String name;
	private int age;
    //省略 Getter、Setter等方法
    //...
}


下面是一個泛型方法的定義,具體如下:

/**
 * 泛型方法
 * @author jzman
 */
public class GenercityMethod {
	public static void main(String[] args) {
		PersonBean bean = new PersonBean("tom",10);
		printA("string");
		printA(bean);
		printB(bean);
	}
	//泛型不指定其超類,由於類型無法確定,只能訪問其信息
	public static <T> void printA(T t) {
		System.out.println(t);
	}
	//泛型指定超類,可以修改其泛型表示的實體信息
	public static <T extends PersonBean> void printB(T t) {
		t.setName("haha");
		System.out.println(t);
	}
}

輸出結果如下:

string
PersonBean [name=tom, age=10]
PersonBean [name=haha, age=10]

由於泛型方法 printA 具體類型不確定,不能修改其泛型信息,printB 指定了其超類,一定程度上可以修改泛型表示的具體類型的信息。泛型可以定義在方法中,是否有泛型方法,與其所在的類是否有泛型沒有關係

泛型繼承#

子類繼承父類的時候,泛型又該如何處理呢,下面的子類繼承帶泛型父類的幾種情況,具體如下:

/**
 * 泛型父類
 * @author jzman
 */
public abstract class GenercityEClass<T> {
	T name;
	public abstract void print(T t);
}

/**
 * 子類為泛型類,類型在使用時確定
 * @author jzman
 * @param <T>
 */
class Child1<T> extends GenercityEClass<T>{
	T t; //子類的屬性由子類決定
	@Override
	public void print(T t) {
		//父類的屬性隨父類決定
		T name = this.name;
	}
}

/**
 * 子類指定具體類型
 * @author jzman
 */
class Child2 extends GenercityEClass<String>{
	Integer t; //子類的屬性由子類決定
	@Override
	public void print(String t) {
		//父類的屬性隨父類決定
		String name = this.name;
	}
}

/**
 * 父類泛型的擦除
 * 子類是泛型類,父類不指定類型,泛型擦除是使用Object來代替
 * @author jzman
 * @param <T>
 */
class Child3<T/*,T1*/> extends GenercityEClass{
	T t;
	String t1; //子類的屬性由子類決定
	@Override
	public void print(Object t) {
		//父類的屬性隨父類決定
		Object obj = this.name;
	}
}

/**
 * 子類和父類同時泛型擦除
 * @author jzman
 */
class Child4 extends GenercityEClass{
	//只能使用具體類型
	String str; //子類的屬性由子類決定
	@Override
	public void print(Object t) {
		//父類的屬性隨父類決定
		Object obj = this.name;
	}
}

可以得到一些結論:父類中的屬性隨父類而定,子類中的屬性隨子類而定,子類繼承父類時的方法重寫,其相關的類型隨父類而定,這種帶有泛型的操作在繼承時涉及到泛型擦除,將在下文中說明。

泛型擦除#

泛型繼承小節中涉及到泛型的擦除,感覺比較重要,故單獨記錄一下,泛型擦除兩種情況,具體如下:

  1. 繼承或實現(接口)時泛型擦除;
  2. 具體使用時的泛型擦除。

子類繼承父類時,泛型的擦除有兩種情況,具體如下:

  1. 子類泛型,父類泛型擦除
  2. 子類和父類同時泛型擦除

下面是部分代碼,具體如下:

/**
 * 父類泛型的擦除
 * 子類是泛型類,父類不指定類型,泛型擦除是使用Object來代替
 * @author jzman
 * @param <T>
 */
class Child3<T/*,T1*/> extends GenercityEClass{
	T t;
	String t1; //子類的屬性由子類決定
	@Override
	public void print(Object t) {
		//父類的屬性隨父類決定
		Object obj = this.name;
	}
}

/**
 * 子類和父類同時泛型擦除
 * @author jzman
 */
class Child4 extends GenercityEClass{
	//只能使用具體類型
	String str; //子類的屬性由子類決定
	@Override
	public void print(Object t) {
		//父類的屬性隨父類決定
		Object obj = this.name;
	}
}
/**
 * 子類擦除,父類使用泛型(錯誤)
 * @author jzman
 *
class Child5 extends GenercityEClass<T>{
	@Override
	public void print(T t) {
		
	}
}
 */

注意一點,不能父類泛型,子類泛型擦除,只能子類泛型在大於等於父類泛型個數的情況下才能進行泛型擦除。類實現泛型接口與類之間的繼承類似,不再贅述。

下面是具體使用時的泛型擦除,具體如下:

class Child1<T> extends GenercityEClass<T>{
	T t; //子類的屬性由子類決定
	@Override
	public void print(T t) {
		//父類的屬性隨父類決定
		T name = this.name;
	}
	
	public static void main(String[] args) {
		//1.使用時泛型擦除
		Child1 child1 = new Child1();
		Object obj = child1.name;//屬性name為擦除後的類型Object
		//2.使用時指定類型
		Child1<String> child2 = new Child1();
		String name = child2.name;//屬性name為指定的類型 String
	}
}

關於泛型擦除就聊到這。

泛型的高級用法#

泛型的高級用法具體如下:

  1. 限制泛型可用類型
  2. 使用類型通配字符

限制泛型可用類型#

默認可以使用任何類型來實例化一個泛型類對象,也可以對泛型類實例的類型進行限制,語法具體如下:

//限制泛型可用類型
class <T extends AnyClass>{
    
}

上面的 AnyClass 可以是一個類也可以是一個接口,也就是說泛型的類型必須繼承 AnyClass 類或實現 AnyClass 接口,無論是類還是接口,進行類型限制時都是用 extends 關鍵字。下面是案例,具體如下:

/**
 * 泛型可用類型限制
 * @author jzman
 */
public class LimitGenercityClass<T extends List> {
	public static void main(String[] args) {
		// T extends:<上限,T可以是上限的子類或其本身
		LimitGenercityClass<ArrayList<String>> limitClass1 = new LimitGenercityClass<>();
		LimitGenercityClass<LinkedList<String>> limitClass2 = new LimitGenercityClass<>();
		//HashMap沒有實現List接口,故HashMap不能對泛型類實例的類型實例化
//		LimitGenercityClass<HashMap<String,String>> limitClass3 = new LimitGenercityClass<>();	
	}
}

上述代碼中對泛型 T 進行了限制,具體的泛型類型必須實現 List 接口,ArrayList 和 LinkedList 實現了 List 接口,而 HashMap 沒有實現 List 接口,所以 HashMap 不能實例化對應泛型的具體類型。

使用類型通配符#

在泛型機制中提供了類型通配符,主要作用是創建一個泛型類對象時限制這個泛型類的類型繼承或實現某個類或接口,可以使用 ? 通配符來實現,同時也可以使用 extends、super 關鍵字對泛型進行限制,使用類型通配符語法具體如下:

//使用類型通配符
泛型類名稱<? extends AnyClass> a ;

下面是類型通配符的使用,具體如下:

/**
 * 使用通配符
 * ? extends AnyClass:限制泛型的具體類型只能是AnyClass的子類
 * ? super AnyClass:限制泛型的具體類型只能是AnyClass的超類
 * @author jzman
 */
public class CommonCharGenercityClass<T> {
	public static void main(String[] args) {
		//1.通配符用在類型聲明上
		CommonCharGenercityClass<? extends List> commA = null;
		//通配符中使用extends關鍵字限制了泛型只能是List的子類
		commA = new CommonCharGenercityClass<ArrayList<String>>();
		commA = new CommonCharGenercityClass<LinkedList<String>>();
//		commA = new CommonCharGenercityClass<HashMap<String>>();
		
		CommonCharGenercityClass<? super List> commB = null;
		//出錯,通配符中使用super關鍵字限制了泛型只能是 List 的超類才可以,比如Object
//		commB = new CommonCharGenercityClass<ArrayList<String>>();
		commB = new CommonCharGenercityClass<Object>();
		
		List<String> listA = new ArrayList<>();
		listA.add("tom");
		
		List<?> listB = listA;
		//使用了通配符時,由於不確定具體類型,只能獲取和刪除數據,不能添加數據,不能被調用
//		listB.add("new");
		listB.remove(0);
		System.out.println(listB);	
	}
	
	//2.通配符用在方法參數上
	public static void test(CommonCharGenercityClass<? extends List> list) {
		//...
	}
}

類型通配符 ? 可以結合關鍵字 extends 和 super 來實現對泛型具體類型的限制,extends 限制泛型的具體類型應該是目標類型的子類,super 限制泛型的具體類型應該是目標類型的超類,此外,類型通配符不能用在聲明類上。

泛型作用#

  1. 編譯的時候檢查類型安全,提前發現錯誤
  2. 泛型中的類型強制轉換都是自動和隱式的,提高了代碼的重用率。

總結:泛型的類型必須是引用類型,不能是基本類型,泛型的個數可以有多個,可以使用 ?對創建對象時的泛型類型以及方法參數類型進行限制,如使用關鍵字 extendssuper 對泛型的具體類型進行向下限制或向上限制,最後一點,可以聲明泛型數組,但是不能創建泛型數組的實例。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。