1. 程式人生 > >Java泛型方法和型別萬用字元的區別

Java泛型方法和型別萬用字元的區別

泛型方法VS型別萬用字元(兩者可以混用):

     1)你會發現所有能用型別萬用字元(?)解決的問題都能用泛型方法解決,並且泛型方法可以解決的更好:

最典型的一個例子就是:

            a. 型別萬用字元:void func(List<? extends A> list);

            b. 完全可以用泛型方法完美解決:<T extends A> void func(List<T> list);

上面兩種方法可以達到相同的效果(?可以代表範圍內任意型別,而T也可以傳入範圍內的任意型別實參),並且泛型方法更進一步,?泛型物件是隻讀的,而泛型方法裡的泛型物件是可修改的,即List<T> list中的list是可修改的!!

    2) 要說兩者最明顯的區別就是:

         i. ?泛型物件是隻讀的,不可修改,因為?型別是不確定的,可以代表範圍內任意型別;

         ii. 而泛型方法中的泛型引數物件是可修改的,因為型別引數T是確定的(在呼叫方法時確定),因為T可以用範圍內任意型別指定;

注意,前者是代表,後者是指定,指定就是確定的意思,而代表卻不知道代表誰,可以代表範圍內所有型別;

    3) 這樣好像說的萬用字元?一無是處,但是並不是這樣,Java設計型別萬用字元?是有道理的,首先一個最明顯的優點就是?的書寫要比泛型方法簡潔,無需先宣告型別引數,其次它們有各自的應用場景:

         i. 一般只讀就用?,要修改就用泛型方法,例如一個進行修改的典型的泛型方法的例子:

public <T> void func(List<T> list, T t) {

    list.add(t);

    }

         ii. 在多個引數、返回值之間存在型別依賴關係就應該使用泛型方法,否則就應該是萬用字元?:

        具體講就是,如果一個方法的返回值、某些引數的型別依賴另一個引數的型別就應該使用泛型方法,因為被依賴的型別如果是不確定的?,那麼其他元素就無法依賴它),例如:<T> void func(List<? extends T> list, T t);  即第一個引數依賴第二個引數的型別(第一個引數list的型別引數必須是第二個引數的型別或者其子類);

可以看到,Java支援泛型方法和?混用;

這個方法也可以寫成:<T, E extends T> void func(List<E> list, T t);  // 明顯意義是一樣的,只不過這個list可以修改,而上一個list無法修改

總之就是一旦返回值、形參之間存在型別依賴關係就只能使用泛型方法;

        否則就應該使用? ;


    4) 對泛型方法的型別引數進行規約:即有時候可能不必使用泛型方法的地方你不小心麻煩地寫成了泛型方法,而此時你可以將其規約成使用?的最簡形式

         i. 總結地來講就是一句話:只出現一次 & 對它沒有任何依賴

         ii. 例如:<T, E extends T> void func(List<T> l1, List<E> l2);  // 這裡E只在形參中出現了一次(型別引數宣告不算),並且沒有任何其他東西(方法形參、返回值)依賴它,那麼就可以把E規約成?

!!最終規約的結果就是:<T> void func(List<T> l1, List<? extends T> l2);

    5) 一個最典型的應用就是容器賦值方法(Java的API):public static <T> void Collections.copy(List<T> dest, List<? extends T> src) { ... }

!!從src拷貝到dest,那麼dest最好是src的型別或者其父類,因為這樣才能型別相容,並且src只是讀取,沒必要做修改,因此使用?還可以強制避免你對src做不必要的修改,增加的安全性