1. 程式人生 > >java泛型(二)、泛型的內部原理:型別擦除以及型別擦除帶來的問題

java泛型(二)、泛型的內部原理:型別擦除以及型別擦除帶來的問題

參考:java核心技術

一、Java泛型的實現方法:型別擦除

前面已經說了,Java的泛型是偽泛型。為什麼說Java的泛型是偽泛型呢?因為,在編譯期間,所有的泛型資訊都會被擦除掉。正確理解泛型概念的首要前提是理解型別擦出(type erasure)。

Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java位元組碼中是不包含泛型中的型別資訊的。使用泛型的時候加上的型別引數,會在編譯器在編譯的時候去掉。這個過程就稱為型別擦除。

如在程式碼中定義的List<object>和List<String>等型別,在編譯後都會程式設計List。JVM看到的只是List,而由泛型附加的型別資訊對JVM來說是不可見的。Java編譯器會在編譯時儘可能的發現可能出錯的地方,但是仍然無法避免在執行時刻出現型別轉換異常的情況。型別擦除也是Java的泛型實現方法與C++模版機制實現方式之間的重要區別。

可以通過兩個簡單的例子,來證明java泛型的型別擦除。

例1、

public class Test4 {
	public static void main(String[] args) {
		ArrayList<String> arrayList1=new ArrayList<String>();
		arrayList1.add("abc");
		ArrayList<Integer> arrayList2=new ArrayList<Integer>();
		arrayList2.add(123);
		System.out.println(arrayList1.getClass()==arrayList2.getClass());
	}
}
在這個例子中,我們定義了兩個ArrayList陣列,不過一個是ArrayList<String>泛型型別,只能儲存字串。一個是ArrayList<Integer>泛型型別,只能儲存整形。最後,我們通過arrayList1物件和arrayList2物件的getClass方法獲取它們的類的資訊,最後發現結果為true。說明泛型型別String和Integer都被擦除掉了,只剩下了原始型別

例2、

public class Test4 {
	public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		ArrayList<Integer> arrayList3=new ArrayList<Integer>();
		arrayList3.add(1);//這樣呼叫add方法只能儲存整形,因為泛型型別的例項為Integer
		arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");
		for (int i=0;i<arrayList3.size();i++) {
			System.out.println(arrayList3.get(i));
		}
	}
在程式中定義了一個ArrayList泛型型別例項化為Integer的物件,如果直接呼叫add方法,那麼只能儲存整形的資料。不過當我們利用反射呼叫add方法的時候,卻可以儲存字串。這說明了Integer泛型例項在編譯之後被擦除了,只保留了原始型別

二、型別擦除後保留的原始型別

在上面,兩次提到了原始型別,什麼是原始型別?原始型別(raw type)就是擦除去了泛型資訊,最後在位元組碼中的型別變數的真正型別。無論何時定義一個泛型型別,相應的原始型別都會被自動地提供。型別變數被擦除(crased),並使用其限定型別(無限定的變數用Object)替換。

例3:

class Pair<T> {
    private T value;
    public T getValue() {
        return value;
    }
    public void setValue(T  value) {
        this.value = value;
    }
}
Pair<T>的原始型別為:
class Pair {
	private Object value;
	public Object getValue() {
		return value;
	}
	public void setValue(Object  value) {
		this.value = value;
	}
}
因為在Pair<T>中,T是一個無限定的型別變數,所以用Object替換。其結果就是一個普通的類,如同泛型加入java變成語言之前已經實現的那樣。在程式中可以包含不同型別的Pair,如Pair<String>或Pair<Integer>,但是,擦除型別後它們就成為原始的Pair型別了,原始型別都是Object。

從上面的那個例2中,我們也可以明白ArrayList<Integer>被擦除型別後,原始型別也變成了Object,所以通過反射我們就可以儲存字串了。

如果型別變數有限定,那麼原始型別就用第一個邊界的型別變數來替換。

比如Pair這樣宣告

例4:

public class Pair<T extends Comparable& Serializable> {
那麼原始型別就是Comparable

注意:

如果Pair這樣宣告public class Pair<T extends Serializable&Comparable> ,那麼原始型別就用Serializable替換,而編譯器在必要的時要向Comparable插入強制型別轉換。為了提高效率,應該將標籤(tagging)介面(即沒有方法的介面)放在邊界限定列表的末尾。

要區分原始型別和泛型變數的型別

在呼叫泛型方法的時候,可以指定泛型,也可以不指定泛型。

在不指定泛型的情況下,泛型變數的型別為 該方法中的幾種型別的同一個父類的最小級,直到Object。

在指定泛型的時候,該方法中的幾種型別必須是該泛型例項型別或者其子類。

public class Test2{
	public static void main(String[] args) {
		/**不指定泛型的時候*/
		int i=Test2.add(1, 2); //這兩個引數都是Integer,所以T為Integer型別
		Number f=Test2.add(1, 1.2);//這兩個引數一個是Integer,以風格是Float,所以取同一父類的最小級,為Number
		Object o=Test2.add(1, "asd");//這兩個引數一個是Integer,以風格是Float,所以取同一父類的最小級,為Object

                /**指定泛型的時候*/
		int a=Test2.<Integer>add(1, 2);//指定了Integer,所以只能為Integer型別或者其子類
		int b=Test2.<Integer>add(1, 2.2);//編譯錯誤,指定了Integer,不能為Float
		Number c=Test2.<Number>add(1, 2.2); //指定為Number,所以可以為Integer和Float
	}
	
	//這是一個簡單的泛型方法
	public static <T> T add(T x,T y){
		return y;
	}
}


其實在泛型類中,不指定泛型的時候,也差不多,只不過這個時候的泛型型別為Object,就比如ArrayList中,如果不指定泛型,那麼這個ArrayList中可以放任意型別的物件。

舉例:

public static void main(String[] args) {
		ArrayList arrayList=new ArrayList();
		arrayList.add(1);
		arrayList.add("121");
		arrayList.add(new Date());
	}


三、型別擦除引起的問題及解決方法

因為種種原因,Java不能實現真正的泛型,只能使用型別擦除來實現偽泛型,這樣雖然不會有型別膨脹的問題,但是也引起了許多新的問題。所以,Sun對這些問題作出了許多限制,避免我們犯各種錯誤。

1、先檢查,在編譯,以及檢查編譯的物件和引用傳遞的問題

既然說型別變數會在編譯的時候擦除掉,那為什麼我們往ArrayList<String> arrayList=new ArrayList<String>();所建立的陣列列表arrayList中,不能使用add方法新增整形呢?不是說泛型變數Integer會在編譯時候擦除變為原始型別Object嗎,為什麼不能存別的型別呢?既然型別擦除了,如何保證我們只能使用泛型變數限定的型別呢?

java是如何解決這個問題的呢?java編譯器是通過先檢查程式碼中泛型的型別,然後再進行型別擦除,在進行編譯的。

舉個例子說明:

public static  void main(String[] args) {
		ArrayList<String> arrayList=new ArrayList<String>();
		arrayList.add("123");
		arrayList.add(123);//編譯錯誤
	}
在上面的程式中,使用add方法新增一個整形,在eclipse中,直接就會報錯,說明這就是在編譯之前的檢查。因為如果是在編譯之後檢查,型別擦除後,原始型別為Object,是應該執行任意引用型別的新增的。可實際上卻不是這樣,這恰恰說明了關於泛型變數的使用,是會在編譯之前檢查的。

那麼,這麼型別檢查是針對誰的呢?我們先看看引數化型別與原始型別的相容

以ArrayList舉例子,以前的寫法:

ArrayList arrayList=new ArrayList();
現在的寫法:
ArrayList<String>  arrayList=new ArrayList<String>();

如果是與以前的程式碼相容,各種引用傳值之間,必然會出現如下的情況:
ArrayList<String> arrayList1=new ArrayList(); //第一種 情況
ArrayList arrayList2=new ArrayList<String>();//第二種 情況

這樣是沒有錯誤的,不過會有個編譯時警告。

不過在第一種情況,可以實現與 完全使用泛型引數一樣的效果,第二種則完全沒效果。

因為,本來型別檢查就是編譯時完成的。new ArrayList()只是在記憶體中開闢一個儲存空間,可以儲存任何的型別物件。而真正涉及型別檢查的是它的引用,因為我們是使用它引用arrayList1 來呼叫它的方法,比如說呼叫add()方法。所以arrayList1引用能完成泛型型別的檢查。

而引用arrayList2沒有使用泛型,所以不行。

舉例子:

public class Test10 {
	public static void main(String[] args) {
		
		//
		ArrayList<String> arrayList1=new ArrayList();
		arrayList1.add("1");//編譯通過
		arrayList1.add(1);//編譯錯誤
		String str1=arrayList1.get(0);//返回型別就是String
		
		ArrayList arrayList2=new ArrayList<String>();
		arrayList2.add("1");//編譯通過
		arrayList2.add(1);//編譯通過
		Object object=arrayList2.get(0);//返回型別就是Object
		
		new ArrayList<String>().add("11");//編譯通過
		new ArrayList<String>().add(22);//編譯錯誤
		String string=new ArrayList<String>().get(0);//返回型別就是String
	}
}

通過上面的例子,我們可以明白,型別檢查就是針對引用的,誰是一個引用,用這個引用呼叫泛型方法,就會對這個引用呼叫的方法進行型別檢測,而無關它真正引用的物件。

從這裡,我們可以再討論下 泛型中引數化型別為什麼不考慮繼承關係

在Java中,像下面形式的引用傳遞是不允許的:

ArrayList<String> arrayList1=new ArrayList<Object>();//編譯錯誤
ArrayList<Object> arrayList1=new ArrayList<String>();//編譯錯誤

我們先看第一種情況,將第一種情況拓展成下面的形式:

ArrayList<Object> arrayList1=new ArrayList<Object>();
          arrayList1.add(new Object());
          arrayList1.add(new Object());
          ArrayList<String> arrayList2=arrayList1;//編譯錯誤
實際上,在第4行程式碼的時候,就會有編譯錯誤。那麼,我們先假設它編譯沒錯。那麼當我們使用arrayList2引用用get()方法取值的時候,返回的都是String型別的物件(上面提到了,型別檢測是根據引用來決定的。),可是它裡面實際上已經被我們存放了Object型別的物件,這樣,就會有ClassCastException了。所以為了避免這種極易出現的錯誤,Java不允許進行這樣的引用傳遞。(這也是泛型出現的原因,就是為了解決型別轉換的問題,我們不能違背它的初衷)。

在看第二種情況,將第二種情況拓展成下面的形式:

ArrayList<String> arrayList1=new ArrayList<String>();
          arrayList1.add(new String());
          arrayList1.add(new String());
          ArrayList<Object> arrayList2=arrayList1;//編譯錯誤
沒錯,這樣的情況比第一種情況好的多,最起碼,在我們用arrayList2取值的時候不會出現ClassCastException,因為是從String轉換為Object。可是,這樣做有什麼意義呢,泛型出現的原因,就是為了解決型別轉換的問題。我們使用了泛型,到頭來,還是要自己強轉,違背了泛型設計的初衷。所以java不允許這麼幹。再說,你如果又用arrayList2往裡面add()新的物件,那麼到時候取得時候,我怎麼知道我取出來的到底是String型別的,還是Object型別的呢?

所以,要格外注意,泛型中的引用傳遞的問題。

2、自動型別轉換

因為型別擦除的問題,所以所有的泛型型別變數最後都會被替換為原始型別。這樣就引起了一個問題,既然都被替換為原始型別,那麼為什麼我們在獲取的時候,不需要進行強制型別轉換呢?看下ArrayList和get方法:

public E get(int index) {
	RangeCheck(index);
	return (E) elementData[index];
    }

看以看到,在return之前,會根據泛型變數進行強轉。

寫了個簡單的測試程式碼:

public class Test {
public static void main(String[] args) {
ArrayList<Date> list=new ArrayList<Date>();
list.add(new Date());
Date myDate=list.get(0);
}

然後反編了下位元組碼,如下
public static void main(java.lang.String[]);
Code:
0: new #16 // class java/util/ArrayList
3: dup
4: invokespecial #18 // Method java/util/ArrayList."<init
:()V
7: astore_1
8: aload_1
9: new #19 // class java/util/Date
12: dup
13: invokespecial #21 // Method java/util/Date."<init>":()

16: invokevirtual #22 // Method java/util/ArrayList.add:(L
va/lang/Object;)Z
19: pop
20: aload_1
21: iconst_0
22: invokevirtual #26 // Method java/util/ArrayList.get:(I
java/lang/Object;
25: checkcast #19 // class java/util/Date
28: astore_2
29: return

看第22 ,它呼叫的是ArrayList.get()方法,方法返回值是Object,說明型別擦除了。然後第25,它做了一個checkcast操作,即檢查型別#19, 在在上面找#19引用的型別,他是
9: new #19 // class java/util/Date
是一個Date型別,即做Date型別的強轉。
所以不是在get方法裡強轉的,是在你呼叫的地方強轉的。

附關於checkcast的解釋:
checkcast checks that the top item on the operand stack (a reference to an object or array) can be cast to a given type. For example, if you write in Java:

return ((String)obj);

then the Java compiler will generate something like:

aload_1 ; push -obj- onto the stack
checkcast java/lang/String ; check its a String
areturn ; return it

checkcast is actually a shortand for writing Java code like:

if (! (obj == null || obj instanceof <class>)) {
throw new ClassCastException();
}
// if this point is reached, then object is either null, or an instance of
// <class> or one of its superclasses.

3、型別擦除與多型的衝突和解決方法

現在有這樣一個泛型類:

class Pair<T> {
	private T value;
	public T getValue() {
		return value;
	}
	public void setValue(T value) {
		this.value = value;
	}
}

然後我們想要一個子類繼承它
class DateInter extends Pair<Date> {
	@Override
	public void setValue(Date value) {
		super.setValue(value);
	}
	@Override
	public Date getValue() {
		return super.getValue();
	}
}
在這個子類中,我們設定父類的泛型型別為Pair<Date>,在子類中,我們覆蓋了父類的兩個方法,我們的原意是這樣的:

將父類的泛型型別限定為Date,那麼父類裡面的兩個方法的引數都為Date型別:“

	public Date getValue() {
		return value;
	}
	public void setValue(Date value) {
		this.value = value;
	}
 
所以,我們在子類中重寫這兩個方法一點問題也沒有,實際上,從他們的@Override標籤中也可以看到,一點問題也沒有,實際上是這樣的嗎?

分析:

實際上,型別擦除後,父類的的泛型型別全部變為了原始型別Object,所以父類編譯之後會變成下面的樣子:

class Pair {
	private Object value;
	public Object getValue() {
		return value;
	}
	public void setValue(Object  value) {
		this.value = value;
	}
}
再看子類的兩個重寫的方法的型別:
        @Override
	public void setValue(Date value) {
		super.setValue(value);
	}
	@Override
	public Date getValue() {
		return super.getValue();
	}
先來分析setValue方法,父類的型別是Object,而子類的型別是Date,引數型別不一樣,這如果實在普通的繼承關係中,根本就不會是重寫,而是過載。
我們在一個main方法測試一下:
public static void main(String[] args) throws ClassNotFoundException {
		DateInter dateInter=new DateInter();
		dateInter.setValue(new Date());                
                dateInter.setValue(new Object());//編譯錯誤
 }
如果是過載,那麼子類中兩個setValue方法,一個是引數Object型別,一個是Date型別,可是我們發現,根本就沒有這樣的一個子類繼承自父類的Object型別引數的方法。所以說,卻是是重寫了,而不是過載了。

為什麼會這樣呢?

原因是這樣的,我們傳入父類的泛型型別是Date,Pair<Date>,我們的本意是將泛型類變為如下:

class Pair {
	private Date value;
	public Date getValue() {
		return value;
	}
	public void setValue(Date value) {
		this.value = value;
	}
}
然後再子類中重寫引數型別為Date的那兩個方法,實現繼承中的多型。

可是由於種種原因,虛擬機器並不能將泛型型別變為Date,只能將型別擦除掉,變為原始型別Object。這樣,我們的本意是進行重寫,實現多型。可是型別擦除後,只能變為了過載。這樣,型別擦除就和多型有了衝突。JVM知道你的本意嗎?知道!!!可是它能直接實現嗎,不能!!!如果真的不能的話,那我們怎麼去重寫我們想要的Date型別引數的方法啊。

於是JVM採用了一個特殊的方法,來完成這項功能,那就是橋方法

首先,我們用javap -c className的方式反編譯下DateInter子類的位元組碼,結果如下:

class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
  com.tao.test.DateInter();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method com/tao/test/Pair."<init>"
:()V
       4: return

  public void setValue(java.util.Date);  //我們重寫的setValue方法
    Code:
       0: aload_0
       1: aload_1
       2: invokespecial #16                 // Method com/tao/test/Pair.setValue
:(Ljava/lang/Object;)V
       5: return

  public java.util.Date getValue();    //我們重寫的getValue方法
    Code:
       0: aload_0
       1: invokespecial #23                 // Method com/tao/test/Pair.getValue
:()Ljava/lang/Object;
       4: checkcast     #26                 // class java/util/Date
       7: areturn

  public java.lang.Object getValue();     //編譯時由編譯器生成的巧方法
    Code:
       0: aload_0
       1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去呼叫我們重寫的getValue方法
;
       4: areturn

  public void setValue(java.lang.Object);   //編譯時由編譯器生成的巧方法
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #26                 // class java/util/Date
       5: invokevirtual #30                 // Method setValue:(Ljava/util/Date;   去呼叫我們重寫的setValue方法
)V
       8: return
}
從編譯的結果來看,我們本意重寫setValue和getValue方法的子類,竟然有4個方法,其實不用驚奇,最後的兩個方法,就是編譯器自己生成的橋方法。可以看到橋方法的引數型別都是Object,也就是說,子類中真正覆蓋父類兩個方法的就是這兩個我們看不到的橋方法。而打在我們自己定義的setvalue和getValue方法上面的@Oveerride只不過是假象。而橋方法的內部實現,就只是去呼叫我們自己重寫的那兩個方法。

所以,虛擬機器巧妙的使用了巧方法,來解決了型別擦除和多型的衝突。

不過,要提到一點,這裡面的setValue和getValue這兩個橋方法的意義又有不同。

setValue方法是為了解決型別擦除與多型之間的衝突。

而getValue卻有普遍的意義,怎麼說呢,如果這是一個普通的繼承關係:

那麼父類的setValue方法如下:

public ObjectgetValue() {
		return super.getValue();
	}
而子類重寫的方法是:
public Date getValue() {
		return super.getValue();
	}
其實這在普通的類繼承中也是普遍存在的重寫,這就是協變。

關於協變:。。。。。。

並且,還有一點也許會有疑問,子類中的巧方法  Object   getValue()和Date getValue()是同 時存在的,可是如果是常規的兩個方法,他們的方法簽名是一樣的,也就是說虛擬機器根本不能分別這兩個方法。如果是我們自己編寫Java程式碼,這樣的程式碼是無法通過編譯器的檢查的,但是虛擬機器卻是允許這樣做的,因為虛擬機器通過引數型別和返回型別來確定一個方法,所以編譯器為了實現泛型的多型允許自己做這個看起來“不合法”的事情,然後交給虛擬器去區別。


4、泛型型別變數不能是基本資料型別

不能用型別引數替換基本型別。就比如,沒有ArrayList<double>,只有ArrayList<Double>。因為當型別擦除後,ArrayList的原始型別變為Object,但是Object型別不能儲存double值,只能引用Double的值。


5、執行時型別查詢

舉個例子:

ArrayList<String> arrayList=new ArrayList<String>();  

因為型別擦除之後,ArrayList<String>只剩下原始型別,泛型資訊String不存在了。

那麼,執行時進行型別查詢的時候使用下面的方法是錯誤的

if( arrayList instanceof ArrayList<String>)  

java限定了這種型別查詢的方式
if( arrayList instanceof ArrayList<?>)  

? 是萬用字元的形式 ,將在後面一篇中介紹。

6、異常中使用泛型的問題

1、不能丟擲也不能捕獲泛型類的物件。事實上,泛型類擴充套件Throwable都不合法。例如:下面的定義將不會通過編譯:

public class Problem<T> extends Exception{......}
為什麼不能擴充套件Throwable,因為異常都是在執行時捕獲和丟擲的,而在編譯的時候,泛型資訊全都會被擦除掉,那麼,假設上面的編譯可行,那麼,在看下面的定義:
try{
}catch(Problem<Integer> e1){
。。
}catch(Problem<Number> e2){
...
} 
型別資訊被擦除後,那麼兩個地方的catch都變為原始型別Object,那麼也就是說,這兩個地方的catch變的一模一樣,就相當於下面的這樣
try{
}catch(Problem<Object> e1){
。。
}catch(Problem<Object> e2){
...
這個當然就是不行的。就好比,catch兩個一模一樣的普通異常,不能通過編譯一樣:
try{
}catch(Exception e1){
。。
}catch(Exception  e2){//編譯錯誤
...

2、不能再catch子句中使用泛型變數

public static <T extends Throwable> void doWork(Class<T> t){
        try{
            ...
        }catch(T e){ //編譯錯誤
            ...
        }
   }
因為泛型資訊在編譯的時候已經變味原始型別,也就是說上面的T會變為原始型別Throwable,那麼如果可以再catch子句中使用泛型變數,那麼,下面的定義呢:
public static <T extends Throwable> void doWork(Class<T> t){
        try{
            ...
        }catch(T e){ //編譯錯誤
            ...
        }catch(IndexOutOfBounds e){
        }                         
 }
根據異常捕獲的原則,一定是子類在前面,父類在後面,那麼上面就違背了這個原則。即使你在使用該靜態方法的使用T是ArrayIndexOutofBounds,在編譯之後還是會變成Throwable,ArrayIndexOutofBounds是IndexOutofBounds的子類,違背了異常捕獲的原則。所以java為了避免這樣的情況,禁止在catch子句中使用泛型變數。

但是在異常宣告中可以使用型別變數。下面方法是合法的。

   public static<T extends Throwable> void doWork(T t) throws T{
       try{
           ...
       }catch(Throwable realCause){
           t.initCause(realCause);
           throw t; 
       }
  }
上面的這樣使用是沒問題的。

7、陣列(這個不屬於型別擦除引起的問題)

不能宣告引數化型別的陣列。如:
 

  Pair<String>[] table = newPair<String>(10); //ERROR
這是因為擦除後,table的型別變為Pair[],可以轉化成一個Object[]。
  
Object[] objarray =table;
  陣列可以記住自己的元素型別,下面的賦值會丟擲一個ArrayStoreException異常。
   
objarray ="Hello"; //ERROR
  對於泛型而言,擦除降低了這個機制的效率。下面的賦值可以通過陣列儲存的檢測,但仍然會導致型別錯誤。  
 objarray =new Pair<Employee>();
提示:如果需要收集引數化型別物件,直接使用ArrayList:ArrayList<Pair<String>>最安全且有效。

8、泛型型別的例項化

不能例項化泛型型別。如,

    first = new T(); //ERROR

   是錯誤的,型別擦除會使這個操作做成new Object()。
   不能建立一個泛型陣列。
  
  public<T> T[] minMax(T[] a){
       T[] mm = new T[2]; //ERROR
       ...
  }

   類似的,擦除會使這個方法總是構靠一個Object[2]陣列。但是,可以用反射構造泛型物件和陣列。
   利用反射,呼叫Array.newInstance:
 publicstatic <T extends Comparable> T[]minmax(T[] a)

    {

       T[] mm == (T[])Array.newInstance(a.getClass().getComponentType(),2);

        ...

       // 以替換掉以下程式碼

       // Obeject[] mm = new Object[2];

       // return (T[]) mm;

    }
9、型別擦除後的衝突

1、

當泛型型別被擦除後,建立條件不能產生衝突。如果在Pair類中新增下面的equals方法:

class Pair<T>   {
	public boolean equals(T value) {
		return null;
	}
	
}
考慮一個Pair<String>。從概念上,它有兩個equals方法:

booleanequals(String); //在Pair<T>中定義

boolean equals(Object); //從object中繼承

但是,這只是一種錯覺。實際上,擦除後方法

boolean equals(T)

變成了方法 boolean equals(Object)

這與Object.equals方法是衝突的!當然,補救的辦法是重新命名引發錯誤的方法。

2、

泛型規範說明提及另一個原則“要支援擦除的轉換,需要強行制一個類或者型別變數不能同時成為兩個介面的子類,而這兩個子類是同一接品的不同引數化。”

下面的程式碼是非法的:

class Calendar implements Comparable<Calendar>{ ... }
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{...} //ERROR
GregorianCalendar會實現Comparable<Calender>和Compable<GregorianCalendar>,這是同一個介面的不同引數化實現。

這一限制與型別擦除的關係並不很明確。非泛型版本:

class Calendar implements Comparable{ ... }
class GregorianCalendar extends Calendar implements Comparable{...} //ERROR
是合法的。

10、泛型在靜態方法和靜態類中的問題

泛型類中的靜態方法和靜態變數不可以使用泛型類所宣告的泛型型別引數

舉例說明:

    public class Test2<T> {  
        public static T one;   //編譯錯誤  
        public static  T show(T one){ //編譯錯誤  
            return null;  
        }  
    }  
因為泛型類中的泛型引數的例項化是在定義物件的時候指定的,而靜態變數和靜態方法不需要使用物件來呼叫。物件都沒有建立,如何確定這個泛型引數是何種型別,所以當然是錯誤的。

但是要注意區分下面的一種情況:

    public class Test2<T> {  
      
        public static <T >T show(T one){//這是正確的  
            return null;  
        }  
    }  
因為這是一個泛型方法,在泛型方法中使用的T是自己在方法中定義的T,而不是泛型類中的T。