1. 程式人生 > >學習筆記之《Java核心技術卷I》---- 第八章 泛型程式設計

學習筆記之《Java核心技術卷I》---- 第八章 泛型程式設計

  • 泛型類的定義格式:class Pair<T>{ }
  • 普通類中泛型方法的定義:public static <T> T getMiddle(T... a){ return a[a.length / 2]; }
  • 呼叫方法時,可以使用:ClassName.getMideele("John","Q"); //編譯器有足夠的資訊可以從傳進來的引數("John","Q")推斷出泛型型別T應該為String
  • 對型別變數T設定限定:public static <T extends Comparable> T min(T... a){ ... } 
  • 型別變數設定限定使用關鍵字 extends 而不是implements 解釋:<T extends BoundingType> 表示T應該是繫結型別的子型別。T和繫結型別可以是類,也可以是介面。選擇extends的原因是更接近子類的概念
  • 一個型別變數或萬用字元可以有多個限定,例如:T extends Comparable & Serializable 限定型別用“&”分隔,而逗號用來分隔型別變數
  • 在Java的繼承中,可以根據需要擁有多個介面超型別(即一個介面可以extends多個介面,一個類也可以implements多個介面),但在泛型中,限定類(即BoundingType)至多有一個類。如果用一個類作為限定,它必須是限定列表中的第一個(相當於某個類既繼承一個超類,又實現多個介面,必須是先寫extends ClassName,再寫implements InterfaceName
    )。並且為了提高效率,應該將標籤介面(即沒有方法的介面)放在邊界列表的末尾
  • Java泛型型別擦除機制:擦除型別變數,並替換為限定型別(無限定的變數用Object)
class pair<T>{
	private T first;
	private T second;
	public pair(T first,T second) {
		// TODO Auto-generated constructor stub
		this.first = first;
		this.second = second;
	}
}

//經泛型擦除後變為:

class pair{
	private Object first;
	private Object second;
	public pair(Object first,Object second) {
		// TODO Auto-generated constructor stub
		this.first = first;
		this.second = second;
	}
}
  •  當程式呼叫泛型方法時,如果擦除返回型別,編譯器自動插入強制型別轉換。在虛擬機器中,用引數型別和返回型別確定一個方法
  • 在程式碼進入jvm之前,與泛型相關的資訊會被擦除,但在編譯階段,泛型資訊仍然存在。程式碼:
//a1,a2的原始型別相同,但它們並不是同一種類
public class Test1 {
	public static void main(String[] args) throws FileNotFoundException{
		A<String> a1 = new A<>();
		A<Integer> a2 = new A<>();
//使用getClass返回原始型別
	System.out.println(a1.getClass().getSimpleName().equals(a2.getClass().getSimpleName()));//True
		System.out.println(a2.getClass().getSimpleName());//A
	}
}
class A<T>{
}
  • 虛擬機器中沒有泛型,只有普通的類和方法
  • 所有的型別引數都用它們的限定型別轉換 
  • 不能用基本型別例項化型別引數
  • 不能建立引數化型別的陣列,但可以宣告引數化型別陣列的變數。如果非要建立帶型別變數的物件陣列,可以使用ArrayList建立
A<String>[] table1 = new A<String>[10];//錯誤
A<String>[] table2;//正確
ArrayList<A<String>> list = new ArrayList<>();//正確
  • 不能例項化型別變數,即 new T(...),new T[...]或T.class都是非法的
  • 不能在靜態域或靜態方法中引用型別變數
  • 不能丟擲或捕獲泛型類的例項
  • 關於帶有超型別限定的萬用字元可以向泛型物件寫入,帶有子型別限定的萬用字元可以從泛型物件讀取的理解:
public class Test1 {
	public static void main(String[] args) {
		ArrayList<? extends Employee> list1 = new ArrayList<>();
		list1.add(new Executive());//報錯
		list1.add(null);//將報錯行註釋後正確
		System.out.println(list1.get(0));//將報錯行註釋後輸出null
	}
}
class Employee{
	
}
class Manager extends Employee{
	
}
class Executive extends Manager{
	
}

當前討論為extends關鍵字對於get和set的情況。建了一個list1,指定其記憶體放的元素為Employee類及其子類,編譯器只知道是Employee類及其子類,並不知道是Employee類、Manager類或者是Executive類,也就是說編譯器此時並不知道Employee類的子類有哪些!!!。所以當你試圖往裡面加入元素的時候,編譯器會表示 “我不知道具體該存什麼值,所以我不能把你放進去”。可能你會覺得,可以新增一個Executive類的物件,因為list1存放的是上面三種類的哪個,Executive都可以自動轉換為它們(子類向父類可以自動向上轉換);但是,假如我再有一個類是 Executive1 繼承了 Manager,那麼此時你存放Executive顯然就是不適當的(因為此時list1存放的可能是Executive1,而它們之間是不能相互自動轉換的),存放Executive1同理。最終要得,我還是覺得應該是編譯器不知道Employee的子類有哪些!!!而list1可以新增null,那是因為null可以表示任何物件,因此無論你存放的是什麼型別,null都可以往list1中新增。最後get方法,返回的是一個物件,這個物件所屬的類是Employee類的子類,因此用一個Employee變數接收即可,所以get方法可以正確表示。

public class Test1 {
	public static void main(String[] args) {
		ArrayList<? super Executive> list1 = new ArrayList<>();
		list1.add(new Executive());//正確編譯
		System.out.println(list1.get(0));//將報錯行註釋後輸出[email protected]
	}
}
class Employee{
	
}
class Manager extends Employee{
	
}
class Executive extends Manager{
	
}

現在討論super關鍵字對於get和set的情況。list1存放的是Executive的所有父類及其本身類,因此,無論你存放的是Executive還是它的父類,都是可以新增Executive物件(子類向父類自動轉換), 對於get方法,也只需要把結果用一個Object型別的變數接收即可(因為Object類是Executive類的最高子類

綜上,我認為:無論是super還是extends,get()方法都是可以使用的;但是set()方法只對super使用(不考慮set(null)的話)

  • 生產者使用extends,消費者使用super。首先,生產者和消費者都是佇列,消費者佇列從生產者佇列中取值並放到消費者佇列中,因此生產者佇列必需得提供get方法,生產者佇列必需得提供add方法或者set方法。詳細等看了後面的執行緒章節再細說
  • 萬用字元 “?”不是型別變數,不能在編寫程式碼中用“?”作為一種型別
  • 泛型和反射,以後再看