Java表示式的陷阱——泛型引起的錯誤
阿新 • • 發佈:2018-12-25
4、泛型引起的錯誤
泛型是Java5新增的知識點,它允許在使用Java類、呼叫方法時傳入一個型別引數,這樣就可以讓Java類、呼叫方法動態地改變型別。4、1 原始型別變數的賦值
在嚴格的泛型程式中,使用帶泛型宣告的類時應該總是為止指定型別實參,但為了與之前版本Java程式碼保持一致,Java也允許使用帶泛型宣告的類時不指定型別引數。如果使用帶泛型宣告的類時沒有傳入型別引數,那麼這個型別引數預設是宣告該引數時指定的第一個上限型別,這個型別引數也被稱為raw type(原始型別)。輸出結果為: Java物件1import java.util.ArrayList; import java.util.List; public class RawTypeTest { public static void main(String[] args) { List list = new ArrayList(); list.add("Java物件1"); list.add("Java物件2"); list.add("Java物件3"); List<Integer> intList = list; for ( int i = 0 ; i < intList.size() ; i ++ ) { System.out.println(intList.get(i)); } } }
Java物件2
Java物件3
上面程式中先定義了一個不帶泛型資訊的List集合,其中所有的集合元素都是String型別。然後將List集合賦給List<Integer>變數,但是可以正常輸出intList集合的三個字串元素。 通過上面介紹可以看出,當程式把一個原始型別的變數賦給一個帶泛型資訊的變數時,只要它們的型別保持相容(例如:將List變數賦給List<integer>,無論List集合裡實際包含什麼型別的元素),系統都不會有任何問題。不過需要指出的是,當把一個原始型別的變數(如List變數)賦給帶泛型資訊的變數(如List<Integer>)時會有一個潛在的問題:JVM會把集合裡盛裝的所有元素都當做Integer來處理。上面程式遍歷List<Integer>集合時,只是簡單地輸出每個集合元素,並未涉及集合元素的型別,因此程式並沒有出現異常;否則,程式要麼在執行時出現ClassCastException異常,要麼在編譯時提示編譯錯誤。
import java.util.ArrayList;
import java.util.List;
public class RawTypeTest2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Java物件1");
list.add("Java物件2");
list.add("Java物件3");
List<Integer> intList = list;
for ( int i = 0 ; i < intList.size() ; i ++ ) {
Integer i = intList.get(i);
System.out.println(i);
}
}
}
上面程式遍歷intList集合時,嘗試將每個集合元素都賦給Integer變數。由於intList集合的型別本身就是List<Integer>型別,因此編譯器會將每個集合元素都當成Integer處理。嘗試執行上面程式,將看到如下執行時異常。
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Duplicate local variable i
很明顯,intList所引用的集合裡包含的集合元素的型別是String,而不是Integer,因此程式在執行程式碼時將會引發ClassCastException異常。
import java.util.ArrayList;
import java.util.List;
public class RawTypeTest3 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Java物件1");
list.add("Java物件2");
list.add("Java物件3");
List<Integer> intList = list;
for ( int i = 0 ; i < intList.size() ; i ++ ) {
String str = intList.get(i);
System.out.println(intList.get(i));
}
}
}
嘗試編譯上面程式,將發現編譯器直接提示如下編譯資訊。對於程式中intList集合而言,它的型別是List<Integer>型別,因此編譯器會認為該集合的每個元素都是Integer型別,而上面程式嘗試將該集合元素賦給一個String型別的變數,因此會提示編譯錯誤。
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Type mismatch: cannot convert from Integer to String
總結:
- 當程式把一個原始型別的變數賦給一個帶泛型資訊的變數時,總是可以通過編譯,只是會提示一些警告資訊。
- 當程式試圖訪問帶有泛型宣告的集合的集合元素時,編譯器總是把集合元素當成泛型型別處理,並不關心集合裡元素的實際型別。
- 當程式試圖訪問帶有泛型宣告的集合的集合元素時,JVM會遍歷每個集合元素自動執行強制型別轉化,如果集合元素的實際型別與集合所帶的泛型資訊不匹配,執行時將引發ClassCastException異常。
4、2 原始型別的擦除
當把一個具有泛型資訊的物件賦給另一個沒有泛型資訊的變數時,所有尖括號裡的型別資訊都將被拋棄。比如,將一個List<String>型別的物件轉型為List,則該List對集合元素的型別檢查變成了型別變數的上限(即Object)。class Tree<T extends Number> {
T size;
public Tree(T size) {
this.size = size;
}
public T getSize() {
return size;
}
public void setSize(T size) {
this.size = size;
}
}
public class ErasureTest {
public static void main(String[] args) {
Tree<Integer> a = new Tree<Integer>(6); //①
Integer as = a.getSize();
Tree b = a;
Number size1 = b.getSize();
//Integer size2 = b.getSize(); 報錯:Type mismatch: cannot convert from Number to Integer
}
}
上面程式裡定義了一個帶泛型宣告的Tree類,其型別形參的上限是Number,這個型別形參用來定義Tree類的size屬性。在①行程式碼建立了一個Tree<Integer>物件,所以呼叫a的getSize()方法時返回Integer型別的值。當把a賦給一個不帶泛型資訊的b變數時,編譯器就會丟失a物件的泛型資訊,但因為Tree型別形參的上限是Number類,所以編譯器依然知道b的getSize()方法返回Number型別,但具體是Number的哪個子類就不清楚了。
從上面程式可以看出,當把一個帶泛型資訊的Java物件賦給不帶泛型資訊的變數時,Java程式會發生擦除,這種擦除不僅會擦除使用該Java類時傳入的型別實參,而且會擦除所有泛型資訊,也就是擦除所有尖括號裡的資訊。
import java.util.ArrayList;
import java.util.List;
class AppleTree<T extends Number> {
T size;
public AppleTree() {}
public AppleTree(T size) {
this.size = size;
}
public T getSize() {
return size;
}
public void setSize(T size) {
this.size = size;
}
public List<String> getApple() {
List<String> list = new ArrayList<>();
for ( int i = 0 ; i < 3 ; i ++ ) {
list.add(new AppleTree<Integer>(10 * i).toString());
}
return list;
}
public String toString() {
return "AppleTree[size = " + size + "]";
}
}
public class ErasureTest2 {
public static void main(String[] args) {
AppleTree<Integer> a = new AppleTree<Integer>(6);
for ( String appleTree : a.getApple() ) {
System.out.println(appleTree);
}
AppleTree b = a;
for ( String appleTree : b.getApple() ) {
System.out.println(appleTree);
}
}
}
上面程式的main方法中先建立了一個AppleTree<Integer>物件,程式呼叫該方法的getApple()方法的返回值肯定是List<String>型別的值。將a變數賦給AppleTree型別的b變數時會發生擦除,該AppleTree<Integer>物件將丟失所有的泛型資訊,包括
getApple()方法的返回值型別List<Stirng>裡的尖括號資訊,因此最後會提示“Type mismatch: cannot convert from element type Object to String”。