Java泛型方法


1. 定義泛型方法

(1) 如果你定義了一個泛型(類、接口),那么Java規定,你不能在所有的靜態方法、靜態初塊等所有靜態內容中使用泛型的類型參數。例如:

public class A<T> {
	public static void func(T t) {
	//報錯,編譯不通過
	}
}

(2) 如何在靜態內容(靜態方法)中使用泛型,更一般的問題是,如果類(或者接口)沒有定義成泛型,但是就想在其中某幾個方法中運用泛型(比如接受一個泛型的參數等),該如何解決?

  • 定義泛型方法就像定義泛型類或接口一樣,在定義類名(或者接口名)的時候需要指定我的作用域中誰是泛型參數。例如:public class A<T> { ... }表明在類A的作用域中,T是泛型類型參數。
  • 定義泛型方法,其格式是:修飾符 <類型參數列表> 返回類型 方法名(形參列表) { 方法體 }。例如:public static <T, S> int func(List<T> list, Map<Integer, S> map) { ... },其中T和S是泛型類型參數。
  • 泛型方法的定義和普通方法定義不同的地方在於需要在修飾符和返回類型之間加一個泛型類型參數的聲明,表明在這個方法作用域中誰才是泛型類型參數;
  • 不管是普通的類/接口的泛型定義,還是方法的泛型定義都逃不出兩大要素:
    • 明哪些是泛型類型參數;
    • 這些類型參數在哪里使用。

(3) 類型參數的作用域

  • class A<T> { ... }中T的作用域就是整個A;

  • public <T> func(...) { ... }中T的作用域就是方法func;

  • 類型參數也存在作用域覆蓋的問題,可以在一個泛型模板類/接口中繼續定義泛型方法,例如:

class A<T> {
	// A已經是一個泛型類,其類型參數是T
	public static <T> void func(T t) {
	// 再在其中定義一個泛型方法,該方法的類型參數也是T
	}
}
//當上述兩個類型參數沖突時,在方法中,方法的T會覆蓋類的T,即和普通變量的作用域一樣,內部覆蓋外部,外部的同名變量是不可見的。
//除非是一些特殊需求,一定要將局部類型參數和外部類型參數區分開來,避免發生不必要的錯誤,因此一般正確的定義方式是這樣的:
class A<T> {
	public static <S> void func(S s) {

	}
} 

(4) 泛型方法的類型參數可以指定上限,類型上限必須在類型參數聲明的地方定義上限,不能在方法參數中定義上限。規定了上限就只能在規定范圍內指定類型實參,超出這個范圍就會直接編譯報錯。

  • <T extends X> void func(List<T> list){ ... },正確
  • <T extends X> void func(T t){ ... },正確
  • <T> void func(List<T extends X> list){ ... } ,編譯錯誤

2. 泛型調用

(1) 顯式指定方法的類型參數,類型參數要寫在尖括號中並放在方法名之前。例如:object.<String> func(...),這樣就顯式指定了泛型方法的類型參數為String,那么所有出現類型參數T的地方都將替換成String類型。
(2) 隱式地自動推斷,不指明泛型參數,編譯器根據傳入的實參類型自動推斷類型參數。例如:<T> void func(T t){ ... }隱式調用object.func("name"),根據"name"的類型String推斷出類型參數T的類型是String
(3) 避免歧義,例如:<T> void func(T t1, T t2){ ... }如果這樣調用的話object.func("name", 15); 雖然編譯不會報錯,但是仍然會有很大隱患,T到底應該是String還是Integer存在歧義;
(4) 有些歧義Java是會直接當成編譯錯誤的,即所有和泛型參數有關的歧義,例如:<T> void func(List<T> l1, List<T> l2){...}如果這樣調用的話,object.func(new List<String>(), new List<Integer>()); 這里會有歧義,編譯器無法知道T到底應該是String還是Integer,這種歧義會直接報錯的,編譯無法通過。即泛型方法中,如果類型參數剛好就是泛型參數的類型實參,那么這個類型實參不得有歧義,否則直接編譯報錯。

3. 泛型方法/類型通配符

(1) 你會發現所有能用類型通配符(?)解決的問題都能用泛型方法解決,並且泛型方法可以解決的更好。

  • 類型通配符:void func(List<? extends A> list);
  • 完全可以用泛型方法完美解決:<T extends A> void func(List<T> list);

(2) 兩種方法可以達到相同的效果,“?”可以代表范圍內任意類型,而T也可以傳入范圍內的任意類型實參,並且泛型方法更進一步,“?”泛型對象是只讀的,而泛型方法里的泛型對象是可修改的,即List<T> list中的list是可修改的。

(3) 兩者最明顯的區別

  • “?”泛型對象是只讀的,不可修改,因為“?”類型是不確定的,可以代表范圍內任意類型;
  • 而泛型方法中的泛型參數對象是可修改的,因為類型參數T是確定的(在調用方法時確定),因為T可以用范圍內任意類型指定;

(3) 適用場景

  • 一般只讀就用“?”,要修改就用泛型方法。例如:
public <T> void func(List<T> list, T t) {
	list.add(t);
} 
  • 在多個參數、返回值之間存在類型依賴關系就應該使用泛型方法,否則就應該是通配符“?”。具體就是,如果一個方法的返回值、某些參數的類型依賴另一個參數的類型就應該使用泛型方法,因為被依賴的類型如果是不確定的"?",那么其他元素就無法依賴它。例如:<T> void func(List<? extends T> list, T t);即第一個參數依賴第二個參數的類型(第一個參數list的類型參數必須是第二個參數的類型或者其子類)。
  • <T, E extends T> void func(List<T> l1, List<E> l2); 這里E只在形參中出現了一次(類型參數聲明不算),並且沒有任何其他東西(方法形參、返回值)依賴它,那么就可以把E規約成“?”。規約結果<T> void func(List<T> l1, List<? extends T> l2);
  • 典型應用,容器賦值方法(Java的API):public static <T> void Collections.copy(List<T> dest, List<? extends T> src) { ... }從src拷貝到dest,那么dest最好是src的類型或者其父類,因為這樣才能類型兼容,並且src只是讀取,沒必要做修改,因此使用“?”還可以強制避免對src做不必要的修改,增加的安全性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM