1. 程式人生 > >Java系列之泛型

Java系列之泛型

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

定義泛型類

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

//定義泛型類
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. 使用型別通配字元
1. 限制泛型可用型別

預設可以使用任何型別來例項化一個泛型類物件,也可以對泛型類例項的型別進行限制,語法具體如下:

//限制泛型可用型別
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. 泛型中的型別強制轉換都是自動和隱式的,提高了程式碼的重用率。

總結:泛型的型別必須是引用型別,不能是基本型別,泛型的個數可以有多個,可以使用 ?對建立物件時的泛型型別以及方法引數型別進行限制,如使用關鍵字 extends 和 super 對泛型的具體型別進行向下限制或向上限制,最後一點,可以宣告泛型陣列,但是不能建立泛型陣列的例項。

可以選擇關注微信公眾號:jzman-blog 獲取最新更新,一起交流學習!
jzman-blog