1. 概述
本文将介绍如何在Java中创建一个能存储多种对象类型的ArrayList
。我们还会学习如何向其中添加不同类型的数据,以及如何从ArrayList
中检索数据并将其转换回原始类型。
2. 背景知识
阅读本文需要具备Java集合框架(特别是ArrayList
)的基础知识。建议先了解Java List接口和Java ArrayList指南。
ArrayList
类不直接支持基本数据类型,但可以通过包装类间接支持。通常我们使用特定类型参数(如String
、Integer
)创建ArrayList
,这意味着只能添加该类型的数据。例如,ArrayList<Integer>
不会接受String
、Boolean
或Double
类型的数据。
我们的目标是在单个ArrayList
中存储多种类型的数据。这虽然违背了ArrayList
单类型参数的设计初衷,但通过多种方法仍可实现。下文将详细探讨这些方案。
3. 原始类型 vs 参数化类型
原始类型是不带类型参数的泛型类型。使用原始类型会失去类型安全,可能导致运行时强制转换异常。参数化类型则是带有实际类型参数的泛型实例化。
⚠️ Java规范明确警告:创建List
时应避免使用原始类型,否则会丢失类型安全性。推荐始终使用类型参数声明列表:
/* 原始类型 - 不推荐 */
List myList = new ArrayList<>();
/* 参数化类型 - 推荐 */
List<Object> myList = new ArrayList<>();
4. 使用Object
作为泛型类型
4.1 创建ArrayList
开发者通常使用特定类型参数(如String
、Integer
)创建ArrayList
,以便统一处理数据。但我们的目标是创建支持多种对象类型的ArrayList
。
解决方案是使用所有Java类的父类——Object
类。Object
位于Java类层次结构的顶端,所有类都直接或间接继承自它。
初始化Object
类型参数的ArrayList
:
ArrayList<Object> multiTypeList = new ArrayList<>();
4.2 向ArrayList插入数据
向Object
类型的ArrayList
添加数据时,所有数据都会自动向上转型为Object
类型。我们将尝试添加多种类型的数据:
- 基本数据类型需通过包装类转换
- 其他类型(如
String
、List
、自定义对象)可直接添加
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
检索数据时,需要手动将其转换回原始类型。有两种主要方法:
- 使用
instanceof
关键字 - 使用
getClass()
方法
✅ 本文采用instanceof
进行类型判断和转换(JDK 16+支持模式匹配):
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());
}
程序输出结果:
// 程序输出
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
5. 替代方案
使用Object
类型的ArrayList
可能导致类型转换问题。以下是几种更安全的替代方案:
5.1 使用公共接口作为类型参数
通过定义接口限制可存储的类型。例如,使用Map
接口允许存储不同Map
实现:
ArrayList<Map> diffMapList = new ArrayList<>();
diffMapList.add(new HashMap<>());
diffMapList.add(new TreeMap<>());
diffMapList.add(new LinkedHashMap<>());
5.2 使用父类作为类型参数
使用父类或超类作为类型参数,允许存储所有子类类型。例如,使用Number
类存储数值类型:
ArrayList<Number> myList = new ArrayList<>();
myList.add(1.2);
myList.add(2);
myList.add(-3.5);
5.3 使用自定义包装类作为类型参数
创建自定义包装类,为每种类型提供专用getter/setter方法,确保类型安全:
public class CustomObject {
String classData;
Integer intData;
// 构造方法和getter
}
ArrayList<CustomObject> objList = new ArrayList<>();
objList.add(new CustomObject("String"));
objList.add(new CustomObject(2));
5.4 使用函数式接口
通过函数式接口添加类型验证逻辑。例如,使用Predicate
限制只允许String
和Integer
类型:
@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);
}
}
}
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("跳过输入 - 不允许的类型: " +
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);
输出结果:
// 输出
Integer Data: 2
跳过输入 - 不允许的类型: Double
String Data: String Value
6. 总结
本文探讨了ArrayList
存储多种类型数据的方法。我们学习了:
- 如何使用
Object
类型参数创建多类型ArrayList
- 添加和检索多类型数据的技巧
- 处理多类型
ArrayList
的最佳实践
虽然直接使用Object
类型简单粗暴,但在实际开发中更推荐使用接口、父类或自定义包装类等类型安全的替代方案,避免运行时类型转换的踩坑风险。