標籤:end ati 泛型 不必要 通過 code name 同名 參數聲明
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> { // 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做不必要的修改,增加的安全性。
【轉】Java泛型方法