1. Overview
1.概述
In this tutorial, we’ll learn how to create an ArrayList that can hold multiple object types in Java. We’ll also learn how to add data of multiple types into an ArrayList and then retrieve data from the ArrayList to transform it back to the original data types.
在本教程中,我们将学习如何在 Java 中创建可容纳多种对象类型的 ArrayList。我们还将学习如何将多种类型的数据添加到 ArrayList 中,然后从 ArrayList 中检索数据,将其转换回原始数据类型。
2. Background
2. 背景
A basic understanding of the Collection framework, especially ArrayList, is required for this article. Take a look at the corresponding articles, Java List Interface and Guide to the Java ArrayList, to gain a basic understanding of these classes.
本文需要对集合框架,尤其是 ArrayList 有基本的了解。请参阅相应的文章:Java列表接口和Java数组列表指南,以获得对这些类的基本了解。
The ArrayList class does not support primitive datatypes directly, but it does support them via wrapper classes. The ArrayList is ideally created with a reference class type. It indicates that when adding data to an ArrayList, only that reference class’s data is supported. For instance, an ArrayList<Integer> won’t accept data from the String, Boolean, or Double classes.
ArrayList 类不直接支持原始数据类型,但通过 封装类支持原始数据类型。理想情况下,ArrayList 是通过引用类类型创建的。这表明在向 ArrayList 中添加数据时,只支持该引用类的数据。例如,ArrayList<Integer> 不会接受来自 String、Boolean 或 Double 类的数据。
Our goal is to store data from multiple types in a single ArrayList. This goes against the fundamental nature of an ArrayList with a single type parameter. However, it is still possible to achieve this with a variety of approaches. We’ll go over them in depth in the following sections of this article.
我们的目标是在单个 ArrayList 中存储多种类型的数据。这违背了带有单个类型参数的 ArrayList 的基本性质。不过,我们仍然可以通过多种方法来实现这一目标。我们将在本文的以下章节中对它们进行深入介绍。
3. Raw Type vs. Parameterized Type
3.原始类型与参数化类型
A raw type is a generic type without any type argument. Raw types can be used like regular types without any restrictions, except that certain uses will result in “unchecked” warnings. On the other hand, a parameterized type is an instantiation of a generic type with actual type arguments. Parameterized types can provide a generic or a concrete type argument during the instantiation of a class.
原始类型是一种不带任何类型参数的通用类型。除了某些使用会导致 “未选中 “警告外,原始类型可以像普通类型一样使用,不受任何限制。另一方面,参数化类型是带有实际类型参数的通用类型实例化。参数化类型可以在类的实例化过程中提供泛型或具体类型参数。
The Java specification cautions against using raw types when creating a List because type safety is lost. Casting exceptions at runtime may result from this. When declaring a list, it’s recommended to always use a type parameter:
Java 规范警告在创建 List 时不要使用原始类型,因为这样会失去类型安全性。运行时的铸造异常可能由此产生。在声明 list 时,建议始终使用类型参数:
/* raw type */
List myList = new ArrayList<>();
/* parameterized type */
List<Object> myList = new ArrayList<>();
4. Using Object as the Generic Type
4.使用 Object 作为通用类型
4.1. Creating the ArrayList
4.1.创建数组列表</em
Most frequently, the developer will produce an ArrayList with certain type parameters like String, Integer, or Double. This is so that a single type of data information may be represented, retrieved, and processed with ease. However, our goal is to create an ArrayList with multiple object types, thus being able to store data of many data types in a single ArrayList.
大多数情况下,开发人员会生成一个带有特定类型参数(如 字符串、整数或 双数)的 ArrayList 。这样就可以轻松地表示、检索和处理单一类型的数据信息。但是,我们的目标是创建具有多种对象类型的 数组列表,从而能够在单个 ArrayList 中存储多种数据类型的数据。
To achieve this, we can use the parent class of all Java classes: the Object class. The Object class is the topmost class in the Java class hierarchy, and all other classes are directly or indirectly derived from it.
为此,我们可以使用所有 Java 类的父类:Object 类。Object 类是 Java 类层次结构中最顶层的类,所有其他类都直接或间接地从它派生出来。
Let’s see how to initialize an ArrayList of Object type parameter:
让我们看看如何初始化 Object 类型参数的 ArrayList :
ArrayList<Object> multiTypeList = new ArrayList<>();
4.2. Inserting Data into the ArrayList
4.2.将数据插入 ArrayList 中
In this section, we’ll learn how we can insert the data into an ArrayList. We had created an ArrayList of Object element type, hence, every time we add any type of data into the ArrayList, it’ll first get auto-cast to Object type and then will get stored inside the ArrayList. We’ll attempt to insert data of various object types such as Integer, Double, String, List, and a user-defined custom object.
在本节中,我们将学习如何将数据插入 ArrayList 中。我们创建了一个 Object 元素类型的 ArrayList ,因此,每次我们向 ArrayList 中添加任何类型的数据时,它都将首先自动转换为 Object 类型,然后存储到 ArrayList 中。我们将尝试插入各种对象类型的数据,如 整数、双倍、字符串、列表和用户定义的自定义对象。
Furthermore, we already know that primitive data types such as int and double cannot be directly stored in an ArrayList, so we’ll convert them using their respective wrapper classes. These wrapper classes are then typecast into the Object class and stored in an ArrayList.
此外,我们已经知道 int 和 double 等原始数据类型不能直接存储在 ArrayList 中,因此我们将使用它们各自的封装类对其进行转换。然后将这些封装类键入 Object 类,并存储在 ArrayList 中。
Other types, such as String, List, and CustomObject, are type parameters in and of themselves, so they can be added directly. However, these elements will also be typecast into the Object before being stored.
其他类型,如 字符串、列表和 自定义对象,本身就是类型参数,因此可以直接添加。不过,这些元素在存储前也将被类型转换到 Object 中。
Let’s look at the code example:
让我们看看代码示例:
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. Retrieving Data from the ArrayList
4.3.从 ArrayList 中读取数据
We’ll also discover how to retrieve the information from the ArrayList of Object type parameter and put it back into the various type parameters from which it was originally added. The ArrayList‘s data elements will all be of the static Object element type.
我们还将了解如何从 Object 类型参数的 ArrayList 中检索信息,并将其放回最初添加信息的各种类型参数中。ArrayList 的数据元素都将是静态 Object 元素类型。
The developer will be in charge of casting it back to the appropriate data type for additional processing. We can accomplish this in two different ways: using the instanceof keyword and the getClass() method.
开发人员将负责将其转换回适当的数据类型,以便进行额外处理。我们可以通过两种不同的方法实现这一目的:使用 instanceof 关键字和 getClass() 方法。
When we use the getClass() method, it returns the type of the data element’s class. We can compare it and then transform the data back to the appropriate data type. When we use instanceof, it compares the left-side element to the right-side type parameter and returns a boolean result.
当我们使用 getClass() 方法时,它会返回数据元素的类类型。我们可以对其进行比较,然后将数据转换回相应的数据类型。当我们使用 instanceof 时,它会将左侧元素与右侧类型参数进行比较,并返回 boolean 结果。
In this tutorial, we’ll use the instanceof keyword to cast the data into the appropriate type parameter. We will then print the value of the data element to see if it was correctly transformed to the original data type.
在本教程中,我们将使用 instanceof 关键字将数据转换为相应的类型参数。然后,我们将打印数据元素的值,以查看它是否被正确转换为原始数据类型。
Let’s look at the code example:
让我们看看代码示例:
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());
}
Please note that we’re using pattern matching, which is a feature from JDK 16.
请注意,我们正在使用模式匹配,这是 JDK 16 的一项功能。
Let’s now check the output of this program:
现在让我们检查一下这个程序的输出结果:
// 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
As we can see, the list elements were looped one by one, and the output was logged as per the relevant data type of each ArrayList element.
正如我们所看到的,列表元素被逐个循环,并根据每个 ArrayList 元素的相关数据类型记录输出。
5. Alternative Approaches
5.替代方法
It’s important to note that utilizing an ArrayList of the Object class can cause problems with data processing after retrieval if the casting or parsing of the relevant type parameter is not handled correctly. Let’s go through a few simple ways we can avoid these issues.
值得注意的是,如果未正确处理相关类型参数的转换或解析,使用 Object 类的 ArrayList 可能会在检索后的数据处理中产生问题。让我们通过几种简单的方法来避免这些问题。
5.1. Using a Common Interface as the Type Parameter
5.1.使用通用接口作为类型参数
One way around these potential problems is to make a list of a predefined or custom interface. This will restrict the entry of data to just those classes that implement the defined interface. As shown below, the list of Map permits data of different representations of the Map interface:
解决这些潜在问题的一种方法是创建一个预定义或自定义 接口的列表。这将使数据输入仅限于那些实现了所定义的 接口的类。如下图所示,Map 列表允许 Map 接口的不同表示形式的数据:
ArrayList<Map> diffMapList = new ArrayList<>();
diffMapList.add(new HashMap<>());
diffMapList.add(new TreeMap<>());
diffMapList.add(new LinkedHashMap<>());
5.2. Using a Parent Class as the Type Parameter
5.2.使用父类作为类型参数
In a different approach, we can define a list of the parent or superclass. It will accept the values from all child class representations. Let’s create a list of Number objects that can accept Integer, Double, Float, and other numeric types:
另一种方法是定义父类或超类的列表。它将接受所有子类表示的值。让我们创建一个 Number 对象列表,它可以接受 Integer, Double, Float, 和其他数值类型:
ArrayList<Number> myList = new ArrayList<>();
myList.add(1.2);
myList.add(2);
myList.add(-3.5);
5.3. Using a Custom Wrapper Class as the Type Parameter
5.3.使用自定义封装类作为类型参数
We can also create a custom wrapper class with the object types we want to allow. Such a class will have dedicated getter and setter methods for all elements of different types. We can then create a List with our class as a type parameter. This is a widely used approach, as it ensures type safety. The below example shows a List of custom wrapper classes for Integer and String elements:
我们还可以创建一个自定义封装类,其中包含我们希望允许的对象类型。该类将为不同类型的所有元素提供专用的 getter 和 setter 方法。然后,我们可以创建一个 List 并将我们的类作为类型参数。这是一种广泛使用的方法,因为它能确保类型安全。下面的示例显示了针对 Integer 和 String 元素的自定义封装类 列表:
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. Using a Functional Interface
5.4.使用功能接口
Yet another solution is to create a list via a functional interface. This approach can be useful if we need some constraints or validation before inserting elements.
还有一种解决方案是通过功能接口创建列表。如果我们在插入元素前需要一些约束或验证,这种方法可能会很有用。
We can write a predicate that checks the List for allowed data types. This Predicate can be used by the abstract method of the functional interface to determine whether to add the data element to the list.
我们可以编写一个谓词来检查 List 中允许的数据类型。该 Predicate 可被功能接口的抽象方法使用,以确定是否将数据元素添加到列表中。
In the functional interface, we can then define a default method that prints the information of the allowed data type:
在功能接口中,我们可以定义一个默认方法,打印允许数据类型的信息:
@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);
}
}
}
Consider the following scenario: We create an ArrayList of objects, but only add type parameters of String and Integer. The functional interface can add the data to the list after the predicate checks the type parameter. We’ll also add a default method for printing the data:
请考虑以下情况:我们创建了一个 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);
Let’s check the output of this approach:
让我们检查一下这种方法的输出结果:
//Output
Integer Data: 2
Skipping input as data is not allowed for class: Double
String Data: String Value
6. Conclusion
6.结论
In this quick article, we took a look at ArrayList’s functionality to store data of various types. We learned how we can create an ArrayList instance with an Object type parameter. In addition to this, we learned how to add or remove elements from ArrayList of multiple object types. We also learned best practices that we can adopt while dealing with the ArrayList with multiple object types.
在这篇短文中,我们了解了 ArrayList 用来存储各种类型数据的功能。我们了解了如何使用 Object 类型参数创建 ArrayList 实例。除此之外,我们还学习了如何从多个对象类型的 ArrayList 中添加或删除元素。我们还学习了在处理具有多个对象类型的 ArrayList 时可以采用的最佳实践。
As usual, all the code samples are available over on GitHub.
与往常一样,所有代码示例均可在 GitHub 上获取。