Java泛型方法

1. 定義泛型方法

(1) 若是你定義了一個泛型(類、接口),那麼Java規定,你不能在全部的靜態方法、靜態初塊等全部靜態內容中使用泛型的類型參數。例如: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) 類型參數的做用域code

  • 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的類型是Stringio

(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作沒必要要的修改,增長的安全性。
相關文章
相關標籤/搜索