你好!在今天的課程中,我們將繼續學習泛型。碰巧這是一個很大的主題,但無處可去 - 這是該語言極其重要的部分:) 當您研究有關泛型的 Oracle 文件或閱讀 Internet 上的指南時,您會遇到這些術語不可具體化類型和可具體化類型。「可具體化」是個什麼樣的字?即使英語一切都很好,你也不太可能遇到它。我們來試試翻譯一下吧!
*謝謝谷歌,你幫了很多忙-_-*
可具體化類型是一種其資訊在運行時完全可用的類型。在 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
因此,當varargs
與泛型結合使用時,有一些重要的特徵。我們來看看這段程式碼:
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>(), // здесь все нормально
"Leonardo da Vinci",
"Vasco de Gama"
);
// а здесь мы получаем предупреждение
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
此方法採用一個列表和任意數量的物件addAll()
作為輸入,然後將所有這些物件加入列表中。在該方法中,我們調用我們的方法兩次。第一次我們新增了兩條常規行。這裡一切都很好。第二次我們增加兩個物件。在這裡我們突然收到一個警告: List<E>
E
main()
addAll()
List
List
Pair<String, String>
Unchecked generics array creation for varargs parameter
這是什麼意思?為什麼我們會收到警告?這與警告有何關係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) {
// ошибка компиляции! Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
原因很明顯——類型安全。您還記得,在建立陣列時,必須指示該陣列將儲存哪些物件(或基元)。
int array[] = new int[10];
在之前的一課中,我們詳細研究了類型擦除機制。因此,在這種情況下,由於擦除類型,我們丟失了Pair
儲存在物件中的資訊對<String, String>
。創建數組是不安全的。將方法與varargs
泛型一起使用時,請務必記住類型擦除及其工作原理。如果您對所編寫的程式碼絕對有信心,並且知道它不會導致任何問題,則可以varargs
使用註釋來停用與其關聯的警告@SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
如果您將此註釋新增到您的方法中,我們先前遇到的警告將不會出現。一起使用泛型時另一個可能的問題varargs
是堆污染。 在以下情況下可能會發生污染:
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> makeHeapPollution() {
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 = makeHeapPollution();
System.out.println(stringsWithHeapPollution.get(0));
}
}
控制台輸出:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
簡單來說,堆污染是指類型 1 的物件應該位於堆上,但由於類型安全錯誤,А
類型 1 的物件最終出現在堆上的情況。B
在我們的範例中,這就是發生的情況。首先,我們建立了一個 Raw 變數numbers
並為其分配了一個通用集合ArrayList<Number>
。之後我們在那裡添加了號碼1
。
List<String> strings = numbers;
在這一行中,編譯器試圖透過發出警告「Unchecked assignment...」來警告我們可能出現的錯誤,但我們忽略了它。結果,我們有一個類型為 的泛型變量List<String>
,它指向類型為 的泛型集合ArrayList<Number>
。這種情況顯然會帶來麻煩!這就是發生的事情。使用新變量,我們將一個字串新增到集合中。堆被污染了 - 我們首先在類型化集合中添加了一個數字,然後添加了一個字串。編譯器向我們發出警告,但我們忽略了它的警告,ClassCastException
僅在程式運行時接收結果。跟它有什麼關係呢varargs
? 與泛型一起使用varargs
很容易導致堆污染。 這是一個簡單的例子:
import java.util.Arrays;
import java.util.List;
public class Main {
static void makeHeapPollution(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");
makeHeapPollution(cars1, cars2);
}
}
這裡發生了什麼事?由於類型擦除,我們的參數表(為了方便起見,我們將其稱為“表”而不是“列表”)是 -
List<String>...stringsLists
- 將變成一個工作表數組 -List[]
具有未知類型(不要忘記 varargs 由於編譯而變成常規數組)。因此,我們可以輕鬆地Object[] array
在方法的第一行中對變數進行賦值 - 類型已從我們的工作表中刪除!現在我們有了一個類型變量Object[]
,我們可以在其中添加任何內容 - Java 中的所有物件都繼承自Object
!現在我們只有一個字串表數組。但由於varargs
類型的使用和刪除,我們可以輕鬆地向它們添加一張數字,這就是我們所做的。結果,我們透過混合不同類型的物件來污染堆。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