建立具有多種物件類型的ArrayList
1. 概述
在本教程中,我們將學習如何在 Java 中建立可以保存多種物件類型的ArrayList 。我們還將學習如何將多種類型的資料新增至ArrayList
中,然後從ArrayList
中檢索資料以將其轉換回原始資料類型。
2. 背景
本文需要對 Collection 框架(尤其是ArrayList
有基本的了解。請參閱對應的文章 Java List Interface 和 Guide to the Java ArrayList,以獲得對這些類別的基本了解。
ArrayList
類別不直接支援原始資料類型,但它透過包裝類別支援它們。 ArrayList
最好使用引用類別類型來建立。它指示向ArrayList
新增資料時,僅支援該引用類別的資料。例如, ArrayList<
Integer>
不會接受來自String
、 Boolean
或Double
類別的資料。
我們的目標是將多種類型的資料儲存在單一ArrayList
中。這違背了具有單一類型參數的ArrayList
的基本性質。然而,仍然可以透過多種方法來實現這一目標。我們將在本文的以下部分中深入討論它們。
3. 原始型別與參數化類型
原始型別是沒有任何型別參數的泛型型別。原始類型可以像常規類型一樣使用,沒有任何限制,但某些使用會導致「未經檢查」的警告。另一方面,參數化類型是具有實際類型參數的泛型類型的實例化。參數化類型可以在類別的實例化過程中提供通用或具體類型參數。
Java 規範警告在建立List
時不要使用原始類型,因為會遺失類型安全性。由此可能會導致運行時的強制轉換異常。聲明清單時,建議始終使用類型參數:
/* raw type */
List myList = new ArrayList<>();
/* parameterized type */
List<Object> myList = new ArrayList<>();
4. 使用Object
作為通用類型
4.1.創建ArrayList
最常見的是,開發人員會產生帶有某些類型參數(例如String
、 Integer
或Double
的ArrayList
。這樣可以輕鬆地表示、檢索和處理單一類型的資料資訊。然而,我們的目標是建立一個具有多種物件類型的ArrayList
,從而能夠在單一ArrayList
中儲存多種資料類型的資料。
為了實現這一點,我們可以使用所有Java類別的父類別: Object
類別。 Object
類別是Java類別層次結構中最頂層的類,所有其他類別都直接或間接派生自它。
讓我們看看如何初始化一個Object
類型參數的ArrayList
:
ArrayList<Object> multiTypeList = new ArrayList<>();
4.2.向ArrayList
插入數據
在本節中,我們將學習如何將資料插入ArrayList
。我們建立了一個Object
元素類型的ArrayList
,因此,每次我們將任何類型的資料新增至ArrayList
時,它都會先自動轉換為Object
類型,然後儲存在ArrayList
中。我們將嘗試插入各種物件類型的數據,例如Integer, Double, String, List,
和使用者定義的自訂物件。
此外,我們已經知道諸如int
和double
類的基本資料類型不能直接儲存在ArrayList
中,因此我們將使用它們各自的包裝類別來轉換它們。然後,這些包裝類別會被類型轉換為Object
類別並儲存在ArrayList
中。
其他類型,例如String, List,
和CustomObject
本身就是類型參數,因此可以直接新增它們。但是,這些元素在儲存之前也會被類型轉換為Object
。
我們看一下程式碼範例:
multiTypeList.add(Integer.valueOf(10));
multiTypeList.add(Double.valueOf(11.5));
multiTypeList.add("String Data");
multiTypeList.add(Arrays.asList(1, 2, 3));
multiTypeList.add(new CustomObject("Class Data"));
multiTypeList.add(BigInteger.valueOf(123456789));
multiTypeList.add(LocalDate.of(2023, 9, 19));
4.3.從ArrayList
檢索數據
我們還將了解如何從Object
類型參數的ArrayList
中檢索信息,並將其放回最初添加它的各種類型參數。 ArrayList
的資料元素都是靜態Object
元素類型。
開發人員將負責將其轉換回適當的資料類型以進行額外處理。我們可以透過兩種不同的方式來完成此操作:使用instanceof
關鍵字和getClass()
方法。
當我們使用getClass()
方法時,它會傳回資料元素的類別的類型。我們可以對其進行比較,然後將資料轉換回適當的資料類型。當我們使用instanceof
時,它會將左側元素與右側類型參數進行比較並傳回一個boolean
結果。
在本教學中,我們將使用instanceof
關鍵字將資料轉換為適當的類型參數。然後,我們將列印資料元素的值,以查看它是否已正確轉換為原始資料類型。
我們看一下程式碼範例:
for (Object dataObj: multiTypeList) {
if (dataObj instanceof Integer intData)
System.out.println("Integer Data : " + intData);
else if (dataObj instanceof Double doubleData)
System.out.println("Double Data : " + doubleData);
else if (dataObj instanceof String stringData)
System.out.println("String Data : " + stringData);
else if (dataObj instanceof List < ? > intList)
System.out.println("List Data : " + intList);
else if (dataObj instanceof CustomObject customObj)
System.out.println("CustomObject Data : " + customObj.getClassData());
else if (dataObj instanceof BigInteger bigIntData)
System.out.println("BigInteger Data : " + bigIntData);
else if (dataObj instanceof LocalDate localDate)
System.out.println("LocalDate Data : " + localDate.toString());
}
請注意,我們使用的是模式匹配,這是 JDK 16 的功能。
現在讓我們檢查一下該程式的輸出:
// Program Output
Integer Data : 10
Double Data : 11.5
String Data : String Data
List Data : [1, 2, 3]
CustomObject Data : Class Data
BigInteger Data : 123456789
LocalDate Data : 2023-09-19
正如我們所看到的,列表元素被逐一循環,並根據每個ArrayList
元素的相關資料類型記錄輸出。
5.替代方法
需要注意的是,如果未正確處理相關類型參數的轉換或解析,則使用Object
類別的ArrayList
可能會導致檢索後的資料處理出現問題。讓我們透過一些簡單的方法來避免這些問題。
5.1.使用通用介面作為類型參數
解決這些潛在問題的一種方法是列出預訂或自訂interface
。這會將資料輸入限制為僅那些實作已定義interface
類別。如下所示, Map
清單允許Map
介面不同表示形式的資料:
ArrayList<Map> diffMapList = new ArrayList<>();
diffMapList.add(new HashMap<>());
diffMapList.add(new TreeMap<>());
diffMapList.add(new LinkedHashMap<>());
5.2.使用父類別作為類型參數
在不同的方法中,我們可以定義父類別或超類別的清單。它將接受來自所有子類別表示的值。讓我們建立一個可以接受Integer, Double, Float,
和其他數字類型的Number
物件清單:
ArrayList<Number> myList = new ArrayList<>();
myList.add(1.2);
myList.add(2);
myList.add(-3.5);
5.3.使用自訂包裝類別作為型別參數
我們還可以使用我們想要允許的物件類型來建立自訂包裝類別。這樣的類別將為不同類型的所有元素提供專用的 getter 和 setter 方法。然後我們可以使用我們的類別作為類型參數來建立一個List
。這是一種廣泛使用的方法,因為它確保類型安全。下面的範例顯示了Integer
和String
元素的自訂包裝類別List
:
public class CustomObject {
String classData;
Integer intData;
// constructors and getters
}
ArrayList<CustomObject> objList = new ArrayList<>();
objList.add(new CustomObject("String"));
objList.add(new CustomObject(2));
5.4.使用函數式介面
另一種解決方案是透過功能介面建立清單。如果我們在插入元素之前需要一些約束或驗證,那麼這種方法會很有用。
我們可以寫一個謂詞來檢查List
允許的資料類型。功能介面的抽象方法可以使用該Predicate
來確定是否將資料元素新增至清單。
在函數式介面中,我們可以定義一個預設方法來列印允許的資料類型的資訊:
@FunctionalInterface
public interface UserFunctionalInterface {
List<Object> addToList(List<Object> list, Object data);
default void printList(List<Object> dataList) {
for (Object data: dataList) {
if (data instanceof String stringData)
System.out.println("String Data: " + stringData);
if (data instanceof Integer intData)
System.out.println("Integer Data: " + intData);
}
}
}
考慮以下場景:我們建立一個物件的ArrayList
,但僅新增String
和Integer
類型參數。函數式介面可以在謂詞檢查類型參數後將資料加入到列表中。我們還將添加列印資料的預設方法:
List<Object> dataList = new ArrayList<>();
Predicate <Object> myPredicate = inputData -> (inputData instanceof String || inputData instanceof Integer);
UserFunctionalInterface myInterface = (listObj, data) -> {
if (myPredicate.test(data))
listObj.add(data);
else
System.out.println("Skipping input as data not allowed for class: " + data.getClass()
.getSimpleName());
return listObj;
};
myInterface.addToList(dataList, Integer.valueOf(2));
myInterface.addToList(dataList, Double.valueOf(3.33));
myInterface.addToList(dataList, "String Value");
myInterface.printList(dataList);
讓我們檢查一下這種方法的輸出:
//Output
Integer Data: 2
Skipping input as data is not allowed for class: Double
String Data: String Value
六,結論
在這篇簡短的文章中,我們了解了ArrayList's
儲存各種類型資料的功能。我們學習如何使用Object
類型參數建立ArrayList
實例。除此之外,我們也學習如何在多種物件類型的ArrayList
中新增或刪除元素。我們也學習了在處理具有多種物件類型的ArrayList
時可以採用的最佳實踐。
與往常一樣,所有程式碼範例都可以在 GitHub 上取得。