你好!在今天的課程中,我們將繼續學習泛型。碰巧,這是一個很大的話題,但無法迴避——它是語言中極其重要的一部分 :) 當您研究關於泛型的 Oracle 文檔或閱讀在線教程時,您會遇到術語不可具體化的類型和具體化類型。具體化類型是一種信息在運行時完全可用的類型。在 Java 中,此類類型包括原始類型、原始類型和非泛型類型。相反,不可具體化的類型是其信息在運行時被擦除並且變得不可訪問的類型。碰巧的是,這些是泛型——
List<String>
,List<Integer>
等等。
順便說一句,你還記得可變參數是什麼嗎?
萬一您忘記了,這是一個變長參數。它們在我們不知道有多少參數可能傳遞給我們的方法的情況下很有用。例如,如果我們有一個具有sum
方法的計算器類。該sum()
方法可以接收 2 個數字,或 3 個,或 5 個,或任意數量。sum()
為每個可能的參數數量重載該方法將是非常奇怪的。相反,我們可以這樣做:
public class SimpleCalculator {
public static int sum(int...numbers) {
int result = 0;
for(int i : numbers) {
result += i;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
System.out.println(sum(2,9));
}
}
控制台輸出:
15
11
這向我們展示了將可變參數與泛型結合使用時有一些重要的特性。讓我們看看下面的代碼:
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
public static void main(String[] args) {
addAll(new ArrayList<String>(), // This is okay
"Leonardo da Vinci",
"Vasco de Gama"
);
// but here we get a warning
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
該方法將 a和任意數量的addAll()
對像作為輸入,然後將所有這些對象添加到列表中。在方法中,我們兩次調用我們的方法。在第一種情況下,我們將兩個普通字符串添加到. 這裡一切都井井有條。在第二種情況下,我們將兩個對象添加到. 但是這裡我們意外收到警告: List<E>
E
main()
addAll()
List
Pair<String, String>
List
Unchecked generics array creation for varargs parameter
這意味著什麼?為什麼我們會收到警告,為什麼會提到 an array
?畢竟,我們的代碼沒有array
!讓我們從第二種情況開始。警告中提到了數組,因為編譯器將可變長度參數 (varargs) 轉換為數組。換句話說,我們addAll()
方法的簽名是:
public static <E> void addAll(List<E> list, E... array)
它實際上看起來像這樣:
public static <E> void addAll(List<E> list, E[] array)
也就是說,在main()
方法中,編譯器將我們的代碼轉換成這樣:
public static void main(String[] args) {
addAll(new ArrayList<String>(),
new String[] {
"Leonardo da Vinci",
"Vasco de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>[] {
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
數組String
就好了。但是Pair<String, String>
數組不是。問題是這Pair<String, String>
是一個不可具體化的類型。在編譯期間,有關類型參數 (<String, String>) 的所有信息都將被刪除。 Java 中不允許創建不可具體化類型的數組。如果您嘗試手動創建一個 Pair<String, String> 數組,您會看到這一點
public static void main(String[] args) {
// Compilation error Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
原因很明顯:類型安全。您會記得,在創建數組時,您肯定需要指定數組將存儲哪些對象(或基元)。
int array[] = new int[10];
在我們之前的一節課中,我們詳細研究了類型擦除。Pair
在這種情況下,類型擦除會導致我們丟失對象存儲對的信息<String, String>
。創建數組是不安全的。 當使用涉及可變參數和泛型的方法時,一定要記住類型擦除及其工作原理。如果您完全確定您編寫的代碼並且知道它不會導致任何問題,您可以使用註釋關閉與 varargs 相關的警告@SafeVarargs
。
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
如果將此註釋添加到您的方法中,我們之前遇到的警告將不會出現。將可變參數與泛型一起使用時可能發生的另一個問題是堆污染。 堆污染可能發生在以下情況:
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> polluteHeap() {
List numbers = new ArrayList<Number>();
numbers.add(1);
List<String> strings = numbers;
strings.add("");
return strings;
}
public static void main(String[] args) {
List<String> stringsWithHeapPollution = polluteHeap();
System.out.println(stringsWithHeapPollution.get(0));
}
}
控制台輸出:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
簡單來說,堆污染就是類型的對象A
應該在堆中,但類型對象B
由於類型安全相關的錯誤而最終出現在堆中。在我們的示例中,這正是發生的情況。首先,我們創建了原始numbers
變量並ArrayList<Number>
為其分配了一個通用集合 ( )。然後我們將數字添加1
到集合中。
List<String> strings = numbers;
在這一行,編譯器試圖通過發出“未經檢查的賦值... ”警告來警告我們可能存在的錯誤,但我們忽略了它。我們最終得到一個 type 的泛型變量List<String>
,它指向一個 type 的泛型集合ArrayList<Number>
。顯然,這種情況會導致麻煩!確實如此。使用我們的新變量,我們將一個字符串添加到集合中。我們現在有堆污染——我們在參數化集合中添加了一個數字,然後是一個字符串。編譯器警告我們,但我們忽略了它的警告。結果,我們ClassCastException
只在程序運行時得到一個。那麼這與可變參數有什麼關係呢? 將可變參數與泛型一起使用很容易導致堆污染。 這是一個簡單的例子:
import java.util.Arrays;
import java.util.List;
public class Main {
static void polluteHeap(List<String>... stringsLists) {
Object[] array = stringsLists;
List<Integer> numbersList = Arrays.asList(66,22,44,12);
array[0] = numbersList;
String str = stringsLists[0].get(0);
}
public static void main(String[] args) {
List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");
polluteHeap(cars1, cars2);
}
}
這裡發生了什麼?由於類型擦除,我們的變長參數
List<String>...stringsLists
變成列表數組,即List[]
未知類型對象的數組(不要忘記可變參數在編譯期間變成常規數組)。正因為如此,我們可以輕鬆地將它分配給Object[] array
方法第一行中的變量——我們列表中對象的類型已被刪除!現在我們有了一個Object[]
變量,我們可以向它添加任何東西,因為 Java 中的所有對像都繼承Object
! 起初,我們只有一個字符串列表數組。但是多虧了類型擦除和我們對可變參數的使用,我們可以輕鬆地添加一個數字列表,我們確實這樣做了。結果,我們通過混合不同類型的對象來污染堆。ClassCastException
當我們嘗試從數組中讀取字符串時,結果將是另一個。控制台輸出:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
這種意想不到的後果可能是由使用 varargs 這種看似簡單的機制引起的:) 至此,今天的課程就結束了。不要忘記解決幾個任務,如果您有時間和精力,請閱讀一些額外的閱讀材料。“ Effective Java ”不會自己閱讀!:) 直到下一次!
GO TO FULL VERSION