使用 Java 中的 Collections.checkedXXX() 確保類型安全
一、簡介
在本文中,我們將探討Collections.checkedXXX()方法,示範它們如何幫助及早捕獲類型不匹配、防止錯誤並增強程式碼可維護性。
在 Java 中,類型安全性對於避免執行時間錯誤並確保程式碼可靠至關重要。這些方法提供了一種在運行時強制集合類型安全的方法。我們將深入研究各種Collections.checkedXXX()方法及其在 Java 應用程式中有效使用它們的好處。
2. 了解 Java 集合中的型別安全
Java 集合中的類型安全性對於防止執行時間錯誤並確保集合僅包含特定類型的元素至關重要。 Java 5 中引入的 Java 泛型提供編譯時類型檢查,使我們能夠定義具有特定類型的集合。例如, List<String>確保只能將字串新增至清單。
然而,在處理原始類型、未經檢查的操作或不使用泛型的遺留程式碼時,我們會損害類型安全。這就是Collections.checkedXXX()方法發揮作用的地方。這些方法透過動態類型檢查包裝集合,從而在執行時強制執行類型安全性。
例如,如果我們加入非字串元素Collections.checkedList(new ArrayList(), String.class)會傳回一個拋出ClassCastException的清單。這額外的運行時檢查層補充了編譯時檢查,儘早捕獲類型不匹配並使程式碼更加健壯。
我們可以使用這些方法,特別是當我們透過 API 公開集合或使用外部來源填充的集合時。它們有助於確保集合中的元素符合預期類型,從而降低錯誤風險並簡化偵錯和維護。
現在我們就來了解這些方法。
3. 理解Collections.checkedCollection()方法
首先,我們檢查一下該方法的簽名:
public static <E> Collection<E> checkedCollection(Collection<E> c, Class<E> type)
此方法傳回指定集合的動態類型安全視圖。如果我們嘗試插入錯誤類型的元素,則會立即發生ClassCastException 。假設在產生動態類型安全視圖之前集合不包含類型錯誤的元素,並且對集合的所有後續存取都是透過視圖進行的,則可以保證集合不會包含類型錯誤的元素。
Java 語言的泛型機制提供編譯時(靜態)類型檢查,但可以透過未經檢查的強制轉換繞過此機制。通常,這不是問題,因為編譯器會警告未經檢查的操作。
然而,在某些情況下,我們需要的不僅僅是靜態類型檢查。例如,考慮這樣一個場景:我們將集合傳遞給第三方程式庫,並且程式庫程式碼不得透過插入錯誤類型的元素來破壞集合。
如果我們用動態類型安全視圖包裝集合,則可以快速識別問題的根源。
例如,我們有這樣的聲明:
Collection<String> c = new Collection<>();
我們可以將其替換為以下表達式,將原始集合包裝到檢查集合中:
Collection<String> c = Collections.checkedCollection(new Collection(), String.class);
如果我們重新執行該程序,當我們將錯誤類型的元素插入集合中時,程式就會失敗。這清楚地表明了問題所在。
使用動態類型安全視圖也有利於偵錯。例如,如果程式遇到ClassCastException ,我們一定會將錯誤類型的元素加入參數化集合中。但是,在我們插入不正確的元素後,此異常可能隨時發生,幾乎無法提供有關問題實際根源的資訊。
4. 使用Collections.checkedCollection()方法
現在讓我們了解如何使用此方法。
假設我們有一個實用程式來驗證資料。這是它的實現:
class DataProcessor {
public boolean checkPrefix(Collection<?> data) {
boolean result = true;
if (data != null) {
for (Object item : data) {
if (item != null && !((String) item).startsWith("DATA_")) {
result = false;
break;
}
}
}
return result;
}
}
checkPrefix()方法檢查集合中的項目是否以前綴“DATA_”開頭。它期望這些項目不為 null 並且為String類型。
現在我們來測試一下:
@Test
void givenGenericCollection_whenInvalidTypeDataAdded_thenFailsAfterInvocation() {
Collection data = new ArrayList<>();
data.add("DATA_ONE");
data.add("DATA_TWO");
data.add(3); // should have failed here
DataProcessor dataProcessor = new DataProcessor();
assertThrows(ClassCastException.class, () -> dataProcessor.checkPrefix(data)); // but fails here
}
此測試將一個String和一個Integer加入到通用集合中,處理時預期會出現ClassCastException 。但是,錯誤發生在checkPrefix()方法中,而不是在新增過程中,因為集合沒有進行類型檢查。
現在讓我們看看當我們嘗試在集合中添加錯誤類型的項目時, checkedCollection()如何幫助我們更早地捕獲此類錯誤:
@Test
void givenGenericCollection_whenInvalidTypeDataAdded_thenFailsAfterAdding() {
Collection data = Collections.checkedCollection(new ArrayList<>(), String.class);
data.add("DATA_ONE");
data.add("DATA_TWO");
assertThrows(ClassCastException.class, () -> {
data.add(3); // fails here
});
DataProcessor dataProcessor = new DataProcessor();
boolean result = dataProcessor.checkPrefix(data);
assertTrue(result);
}
此測試使用Collections.checkedCollection()確保我們僅將字串新增至集合。當嘗試新增整數時,它會立即拋出ClassCastException ,從而在到達checkPrefix()方法之前強制執行類型安全性。
我們無法為此集合指定類型,因為這樣做會破壞契約並導致 IDE 或語法檢查器引發錯誤。
Collections類別提供了多個checkedXXX方法,例如checkedList(), checkedMap() 、 checkedSet() 、 checkedQueue() 、 checkedNavigableMap(), checkedNavigableSet(), checkedSortedMap()和checkedSortedSet() 。這些方法在運行時強制各種集合類型的類型安全。這些方法透過類型檢查包裝集合,確保我們僅添加指定類型的元素,有助於防止ClassCastException並保持類型完整性。
五、退回藏品注意事項
傳回的集合不會將hashCode()和equals()操作委託給後備集合。相反,它依賴於Object的equals()和hashCode()方法。這種方法確保保留這些操作的契約,特別是在支援集合是集合或清單的情況下。
此外,如果指定的集合是可序列化的,則該方法傳回的集合也將是可序列化的。
需要注意的是,由於null被視為任何引用類型的值,因此該方法傳回的集合允許插入null元素,只要後備集合允許即可。
六、結論
在本文中,我們探討了Collections.checkedXXX方法,示範了它們如何在 Java 集合中強制執行執行時間類型安全性。我們看到了checkedCollection()如何透過確保只添加指定類型的元素來防止類型錯誤。
使用這些方法可以增強程式碼可靠性並有助於及早捕獲類型不匹配的情況。透過利用這些工具,我們可以編寫更安全、更健壯的程式碼,並具有更好的運行時類型檢查。
與往常一樣,原始碼可以在 GitHub 上取得。