Java泛型

什麼是泛型
  爲什麼需要泛型
  泛型的繼承關係
  總結
使用泛型
  不使用泛型時
  使用泛型時
  舉個栗子
  總結
編寫泛型
  須知
  編寫步驟
  靜態方法
  多個類型
  總結
擦拭法
  介紹
  侷限
  泛型繼承
  總結
extends通配符
  需求
  注意事項
  總結
super通配符
  介紹
  extends和super
  無限定通配符
  總結
泛型和反射
  反射中的泛型
  泛型數組
  總結

什麼是泛型

爲什麼需要泛型

泛型就是定義一種模版,例如ArrayList<T>

  • 在代碼中爲用到的類創建對應的ArrayList<類型>
ArrayList<String> strList = new ArrayList<String>();
  • 編譯器針對類型作檢查
strList.add("hello");
strList.add(new Integer(123));// compile error
泛型的繼承關係

不能把ArrayList向上轉型爲ArrayList或List

ArrayList<Integer> integerList = new ArrayList<Integer>();
integerList.add(new Integer(123));
// 向上轉型,實際類型還是Integer
ArrayList<Number> numberList = integerList;
// Number可以接收Float類型
numberList.add(new Float(12.34));
// 取值出現異常
Integer n = integerList.get(1); // ClassCastException

ArrayList和ArrayList兩者沒有繼承關係

總結
  • 泛型就是編寫模版代碼來適應任意類型
  • 不必對類型進行強制轉換
  • 編譯器將對類型進行檢查
  • 注意泛型的繼承關係:
    • 可以把ArrayList向上轉型爲List(T不能變)
    • 不能把ArrayList向上轉型爲ArrayList

使用泛型

不使用泛型時
List list = new ArrayList();
list.add("hello");
list.add("World");
Object first = list.get(0);
Object second = list.get(1);
  • List的接口變爲Object類型:
    • void add(Object)
    • Object get(int)
  • 編譯器警告
  • 此時只能把當作Object使用
使用泛型時

定義泛型類型爲

List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
String first = list.get(0);
String second = list.get(1);
  • List的泛型接口變爲強類型:
    • void add(String)
    • String get(int)

可以省略編譯器能自動推斷出的類型

List<Number> list = new ArrayList<Number>();
// 可以省略後面的Number,編譯器可以自動推斷類型
List<Number> list = new ArrayList<>();
栗子

Array.sort()可以對Object[]數組進行排序:

String[] strs = {"Apple", "Pear", "Orange"};
Arrays.sort(strs);
System.out.println(Arrays.toString(strs));
// {"Apple", "Orange", "Pear"}

待排序的元素需要實現Comparable泛型接口:

public interface Comparable<T> {
  	/**
  	*	返回-1:當前實例比參數o小
  	* 返回0:當前實例與參數o相等
  	*	返回1:當前實例比參數o大
  	*/
  	int compareTo(T o);
}
// 待排序元素類實現Comparable<T>接口
class Person implements Comparable<Person> {
  	private String name ;
  	private int score;
  
  	public Person(String name, int score) {
      	this.name = name;
      	this.score = score;
    }
  	// 重寫compareTo方法
  	public int compareTo(Person o) {
      	if (this.score < o.score) {
          	return -1;
        } else if (this.score > o.score) {
          	return 1;
        } else {
           return this.name.compareTo(o.name);
        }
    }
  	@Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
public class  Main {
  	public static void main(String[] args) {
        Person[] list = {new Person("Bob", 89), new Person("Alice", 97), new Person("Lisa", 68)};
      	Arrays.sort(list);
      	System.out.println(Arrays.toString(list));
				// [Person{name='Lisa', score=68}, Person{name='Bob', score=89}, Person{name='Alice', score=97}]
    }
}
總結
  • 使用泛型時,把泛型參數替換爲需要的class類型:
List<String> list = new ArrayList<String>();
  • 可以省略編譯器能自動推斷出的類型
List<String> list = new ArrayList<>();
  • 不指定泛型參數類型時,編譯器會給出警告,且只能將視爲Object類型

編寫泛型

須知
  • 編寫泛型類比普通類要複雜
  • 泛型類一般用在集合類中ArrayList
  • 很少需要編寫泛型類
編寫步驟
  • 按照某種類型(例如String)編寫類
public class Pair {
  	private String first;
  	private String last;
  	public Pair(String first, String last) {
      	this.first = first;
      	this.last = last;
    }
  	public String getFirst() {
      	return first;
    }
  	public String getLast() {
      	return last;
    }
}
  • 標記所有的特定類型(String)
  • 把特定類型替換爲T,並申明
public class Pair<T> {
  	private T first;
  	private T last;
  	public Pair(T first, T last) {
      	this.first = first;
      	this.last = last;
    }
  	public T getFirst() {
      	return first;
    }
  	public T getLast() {
      	return last;
    }
}
靜態方法

泛型類型不能用於靜態方法:

  • 會有編譯錯誤
  • 編譯器無法在靜態字段或靜態方法中使用泛型類型
// 這樣會有編譯錯誤
public static Pair<T> create(T first, T last) {
  	return new Pair<T>(first, last);
}
// 可以在static後面再加一個<T>
// 新加的<T>相當於新申明一個泛型,跟類申明的<T>不是同一個
// 所以最好把這個<T>修改爲<K>
public static <T> Pair<T> create(T first, T last) {
  	return new Pair<T>(first, last);
}
public static <K> Pair<T> create(K first, K last) {
  	return new Pair<K>(first, last);
}
多個類型

定義泛型時可以定義多個類型

  • 使用<T, K>
public class Pair<T, K> {
  	private T first;
  	private K last;
  	public Pair(T first, K last) {
      	this.first = first;
      	this.last = last;
    }
  	public T getFirst() {
      	return first;
    }
  	public K getLast() {
      	return last;
    }
}

Pair<String, Integer> p = new Pair<>("test", 123);
總結
  • 編寫泛型時,需要定義泛型類型
public class Pair<T>{...}
  • 靜態方法不能飲用泛型類型,必須定義其他類型來實現泛型
public static <K> Pair<K> create(K first, K last){...}
  • 泛型可以同時定義多種類型<T, K>
public class Pair<T, K>{...}

擦拭法

介紹

Java泛型的實現方式是擦拭法(Type Erasure)

  • 編譯器把類型視爲Object
  • 編譯器根據實現安全的強制轉型
public static void main(String[] arge) {
  	Pair<String> pair = new Pair<>("Xiao", "Ming");
  	String first = pair.getFirst();
}
// 編譯器實際處理代碼
public static void main(String[] arge) {
  	// 把泛型視爲Object類型
  	Pair pair = new Pair("Xiao", "Ming");
  	// 在需要使用時進行強制轉型
  	String first = (String)pair.getFirst();
}
侷限

擦拭法的侷限:

  • 不能是基本類型,比如int
  • Object字段無法持有基本類型
  • 無法取得帶泛型的Class
Pair<String> s = ...
Pair<Integer> i = ...
Class c1 = s.getClass();
Class c2 = i.getClass();
// c1、c2獲取的都是Pair.class
  • 無法判斷帶泛型的class
// Compile error
if (s instanceof Pair<String>.class) {
}
  • 不能實例話T類型
  • 因爲擦拭後實際上是new Object()
public class Pair<T> {
  	private T first;
  	private T last;
  	public Pair() {
      	// Compile error
      	first = new T():
      	last = new T();
    }
}
Pair<String> pair = new Pair<>():
  • 實例話T類型必須藉助Class

    public class Pair<T> {
      	private T first;
      	private T last;
      	public Pair(Class<T> clazz) {
          	first = clazz.newInstance();
          	last = clazz.newInstance();
        }
    }
    Pair<String> pair = new Pair<>(String.class);
    
泛型繼承

泛型可以繼承自泛型類

  • 父類的類型是Pair
  • 子類的類型是IntPair
  • 子類可以獲取父類的泛型類型Integer
public class IntPair extends Pair<Integer> {
  
}
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
if (t instanceof ParameterizedType) {
  	ParameterizedType pt = (ParameterizedType)t;
  	Type[] types = pt.getActualTypeArguments();
  	Type firstType = types[0];
  	Class<?> typeClass = (Class<?>)firstType;
  	System.out.println(typeClass);	// Integer
}

JDK定義的Type接口實現類關係
在這裏插入圖片描述

總結
  • Java的泛型採用擦拭法實現
  • 擦拭法決定了泛型:
    • 不能是基本類型,例如:int
    • 不能獲取帶泛型類型的Class,例如:Pair。class
    • 不能判斷帶泛型類型的類型,例如:x instanceof Pair
    • 不能實例話 T 類型, 例如:new T()
    • 泛型方法要防止重複定義方法,例如:public boolean equals(T obj)
  • 子類可以獲取父類的泛型類型

extends通配符

需求

前面提到過Pair不是Pair的子類,因此傳入數據只能是指定的類型,但是如果既想傳入Integer,又想傳入Number的類型該如何做呢?

可以使用extend通配符定義泛型,Pair<? extends Number>表示方法可以接收泛型類型爲Number或其子類類型的Pair類

public class Pair<T>{
  	private T first;
  	private T last;
  	...
}
public class PairHelper {
		static int add(Pair<? extends Number> p) {
      	
    }	
}
PairHelper.add(new Pair<Number>(1, 2));
PairHelper.add(new Pair<Integer>(1,2));

在上面add方法中,如果對p調用getFirst方法,則只能使用泛型中的父類類型Number來接收,不能使用某個子類類型如Integer接收,因爲此時無法判斷實際傳入的類型

...
public class PairHelper {
    static int add(Pair<? extends Number> p) {
      	// 使用泛型中的父類來接收
				Number first = p.getFirst();
      	// Compile error
      	Integer fir = p.getFirst();
    }	
}
// getFirst方法實際編譯爲
<? extends Number> getFirst();

在調用setFirst方法時無法傳入任何Number類型的變量,因爲在編譯時無法確定泛型的具體類型,編譯時setFirst方法簽名爲void setFirst(? extends Number);

...
public class PairHelper {
    static int add(Pair<? extends Number> p) {
      	Number first = new Float(1.2f);
      	// Compile error
      	// 無法確定具體類型,但null除外
    		p.setFirst(first)	;
    }	
}
// setFirst方法簽名
void setFirst(? extends Number)
注意事項

使用<? extends Number>的通配符時:

  • 允許調用get方法獲得Number(泛型類型的父類)的飲用
  • 不允許調用set方法傳入Number的飲用
  • 唯一例外:可以調用setFirst(null)
  • 在類上的泛型申明時使用通配符
    • 限定定義Pair是隻能是Number或其子類
public class Pair<T extends Number>{...}
Pair<Number> p1 = new Pair<>(1, 2);
Pair<Double> p1 = new Pair<>(1.2, 2.3);
Pair<String> p1 = new Pair<>("a", "b");	// error
總結
  • 使用類似<? extends Number>通配符作爲方法參數時表示:

    • 方法內部可以調用獲取Number引用的方法
    Number n = objc.getXxx();
    
    • 方法內部無法調用傳入Number飲用的方法(null除外)
    obj.setXxx(Number n); // error
    
  • 使用類似定義泛型類時表示:

    • 泛型類型限定爲Number或Number的子類

super通配符

介紹

使用super通配符以<? super Integer>格式定義泛型,表示接收所有泛型類型爲Integer或Integer超類的類

當調用setFirst方法時,可以傳入Integer類型的數據,因爲Integer的超類可以接收Integer類型

public class Pair<T>{
  	private T first;
  	private T last;
  	...
}
public class PairHelper {
		static int add(Pair<? extends Integer> p) {
      	// 實際類型爲Integer或Integer的超類,可以接收Integer類型
      	p.setFirst(new Integer(2));
    }	
}
PairHelper.add(new Pair<Number>(1, 2));
PairHelper.add(new Pair<Integer>(1,2));

當調用getFirst方法時,方法簽名變爲 ?super Integer getFirst(),方法返值只能確定爲Integer 的超類,無法確定具體類型,因此調用會出錯

public class Pair<T>{
  	private T first;
  	private T last;
  	...
}
public class PairHelper {
		static int add(Pair<? extends Integer> p) {
      	p.getFirst();//Compile error
    }	
}
PairHelper.add(new Pair<Number>(1, 2));
PairHelper.add(new Pair<Integer>(1,2));

當在定義類的泛型時使用super通配符,表示類傳入的泛型類型只能是Integer或Integer類的超類

public class Pair<T super Integer>{...}
Pair<Integer> p1 = new Pair<>(1, 2);
Pair<Number> p1 = new Pair<>(1.2, 2.3);
Pair<String> p1 = new Pair<>("a", "b");// error
extends和super

方法參數爲<? extends T>和方法參數爲 <? Super T>的區別:

  • <? extends T>允許調用方法獲取 T 的引用
  • <? Super T>允許調用方法傳入 T 的引用
無限定通配符
<?>的通配符被稱爲無限定通配符
  • 不允許調用set方法(null除外)
  • 只能調用get方法獲取Object引用
  • Pair<?>和Pair不同
總結
  • 使用類似<? super Integer>通配符作爲方法參數時表示:

    • 方法內部無法調用獲取Integer引用的方法(Object除外)
    Integer n = objc.getXxx();// error
    
    • 方法內部可以調用傳入Integer引用的方法
    obj.setXxx(Integer n); 
    
  • 使用類似定義泛型類時表示:

    • 泛型類型限定爲Integer或Integer的超類
  • 無限定通配符<?>很少使用,可以用替換

泛型和反射

反射中的泛型
  • Class是泛型

    • T newInstance()
    // compile warning
    Class clazz = String.class;
    String str = (String)clazz.newInstance();
    // 使用泛型
    Class<String> clazz = String.class;
    String str = clazz.newInstance();
    
    • Class<? super T> getSuperclass()
    Class<? super String> sup = clazz.getSuperclass();
    
  • Constructor是泛型

Class<integer> clazz = Integer.class;
Constructor<integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);
泛型數組

可以聲明帶泛型的數組,但不能用new創建帶泛型的數組:

Pair<String>[] ps = null;// OK
Pair<String>[] ps = new Pair<String>[2]; // error

必須通過強制轉型實現帶泛型的數組:

@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[])new Pair[2];

不能直接創建T[]數組

  • 擦拭後代碼編爲 new Object[5]
public class Abc<T> {
  	T[] createArray() {
      	// compile error
      	return new T[5];
    }
}
  • 必須藉助Class
public class Abc<T> {
  	T[] createArray(Class<T> cls) {
      	return (T[]) Array.newInstance(cls, 5);
    }
}
  • 利用可變參數創建T[]數組
    • @SafeVarargs消除編譯器警告
public class ArrayHelper {
  	@SafeVarargs
  	static <T> T[] asArray(T... objs) {
      	return objs;
    }
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
總結
  • 部分反射API是泛型
    • Class
    • Constructor
  • 可以聲明帶泛型的數組,但不能直接創建帶泛型的數組,必須強制轉型
  • 可以通過Array.newInstance(Class, int)創建T[]數組,需要強制轉型
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章