當開始深刻研究泛型的時,會發現其實有些東西是沒有意義的。例如,咱們能夠聲明ArrayList.class
,可是卻沒法聲明ArrayList<Integer>.class
。
這是由於泛型的擦除機制形成的,考慮如下的狀況。數組
public class ErasedTypeEquivalence { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println("(c1 == c2) = " + (c1 == c2)); } }
以上代碼中,代表ArrayList<String>
和ArrayList<Integer>
是同一類型。不一樣的類型在行爲方面確定不一樣。例如,若是試着將一個Integer
類型放入ArrayList<String>
,所得的行爲和Integer
類型放入ArrayList<Integer>
徹底不一樣,可是它們仍然是同一類型。
如下的代碼是對這個問題的一個補充。安全
class Frob { } class Fnorkle { } class Quark<Q> { } class Particle<POSITION, MOMENTUM> { } public class LostInfomation { public static void main(String[] args) { List<Frob> list = new ArrayList<>(); Map<Frob,Fnorkle> map = new HashMap<>(); Quark<Fnorkle> quark = new Quark<>(); Particle<Long,Double> p = new Particle<>(); System.out.println(Arrays.toString( list.getClass().getTypeParameters() )); System.out.println(Arrays.toString( map.getClass().getTypeParameters() )); System.out.println(Arrays.toString( quark.getClass().getTypeParameters() )); System.out.println(Arrays.toString( p.getClass().getTypeParameters() )); } }
// Outputs [E] [K, V] [Q] [POSITION, MOMENTUM]
Class.getTypeParameters
是返回一個TypeVariable
對象數組,表示泛型聲明所聲明的類型參數。可是上例的輸出也代表了,這個方法得到的只是作參數佔位符的標識符。學習
在泛型代碼內部,沒法得到任何有關泛型參數類型的信息。
Java的泛型是使用擦除來實現的,這就意味着在使用泛型的時候,任何具體的類型信息都會被擦除。寫代碼時惟一知道就是在使用一個對象。所以,List<String>
和List<Integer>
在運行時事實上是相同的類型。這兩種形式都會被擦除成它們的"原生"類型,即List
。理解擦除以及應該如何處理它,是在學習Java泛型時候的最大阻礙。ui
所以,能夠得到類型參數標識符和泛型類型邊界這樣的信息,可是卻沒法知道用來建立某個特定實例的實際的類型參數。this
class HasF{ public void f(){ System.out.println("HasF.f()"); } } class Manipulator<T> { private T obj; public Manipulator(T obj) { this.obj = obj; } public void manipulate(){ //obj.f() compile error } } public class Manipulation { public static void main(String[] args) { HasF hf = new HasF(); Manipulator<HasF> manipulator = new Manipulator<>(hf); manipulator.manipulate(); } }
因爲有了擦除機制,Java編譯器沒法將manipulate()
必須可以在obj
上調用f()
這一需求映射到HasF
擁有f()
這一事實上,爲了調用f()
,咱們必須協助泛型類,給定泛型類的邊界,以便告知編譯器只能遵循這個邊界的類型。這裏重用了extends
關鍵字。並因爲有了邊界,下面的代碼能夠編譯了。spa
class Manipulator2<T extends HasF> { private T obj; public Manipulator2(T obj) { this.obj = obj; } public void manipulate(){ obj.f(); } }
上面的代碼中,邊界<T extentds HasF>
聲明T
必須具備類型HasF
或者從HasF
導出來的類型,由於這個約束,因此能夠安全地在obj
上調用f了。
這裏說泛型的類型參數將擦除到它的第一邊界(泛型可能有多個邊界)。這裏提到了類型參數的擦除,編譯器實際上會把類型參數替換成它的擦除,就像上面的示例那樣,T
擦除到了HasF
,就像在類的聲明中用HasF
替換成T
同樣。
如同上文所說,咱們能夠不使用泛型,直接將T
替換回會HasF
。code
class Manipulator3 { private HasF obj; public Manipulator2(HasF obj) { this.obj = obj; } public void manipulate(){ obj.f(); } }
上面的代碼也能夠像Manipulator2
中那樣正常工做。可是這並不意味着帶邊界的泛型是毫無心義的。
只有當但願使用的類型參數比某個具體類型(以及它的全部子類型)更加"泛化"時。也就是說,當但願代碼能跨多個類工做的時候,使用泛型纔有幫助。所以,類型參數和它們在有用的泛型代碼中的應用,一般比簡單的類替換要更爲複雜。。可是也不能由於以爲<T extends HasF>
的任何東西都是有缺陷的。
例如,假設某個類有返回T
的方法,那麼泛型在這裏就是有用處的,由於泛型能夠返回確切的類型。例子以下。對象
class ReturnGenericType<T extends HasF> { private T obj; public ReturnGenericType(T obj) { this.obj = obj; } public T getObj() { return obj; } }
因此,必須查看全部的代碼。並肯定它是否"足夠複雜"到必須使用泛型的程度。ip