1. 概述

本文将介绍如何在Java中创建一个能存储多种对象类型的ArrayList。我们还会学习如何向其中添加不同类型的数据,以及如何从ArrayList中检索数据并将其转换回原始类型。

2. 背景知识

阅读本文需要具备Java集合框架(特别是ArrayList)的基础知识。建议先了解Java List接口Java ArrayList指南

ArrayList类不直接支持基本数据类型,但可以通过包装类间接支持。通常我们使用特定类型参数(如StringInteger)创建ArrayList,这意味着只能添加该类型的数据。例如,ArrayList<Integer>不会接受StringBooleanDouble类型的数据。

我们的目标是在单个ArrayList中存储多种类型的数据。这虽然违背了ArrayList单类型参数的设计初衷,但通过多种方法仍可实现。下文将详细探讨这些方案。

3. 原始类型 vs 参数化类型

原始类型是不带类型参数的泛型类型。使用原始类型会失去类型安全,可能导致运行时强制转换异常。参数化类型则是带有实际类型参数的泛型实例化。

⚠️ Java规范明确警告:创建List时应避免使用原始类型,否则会丢失类型安全性。推荐始终使用类型参数声明列表:

/* 原始类型 - 不推荐 */
List myList = new ArrayList<>();

/* 参数化类型 - 推荐 */
List<Object> myList = new ArrayList<>();

4. 使用Object作为泛型类型

4.1 创建ArrayList

开发者通常使用特定类型参数(如StringInteger)创建ArrayList,以便统一处理数据。但我们的目标是创建支持多种对象类型的ArrayList

解决方案是使用所有Java类的父类——Object类。Object位于Java类层次结构的顶端,所有类都直接或间接继承自它。

初始化Object类型参数的ArrayList

ArrayList<Object> multiTypeList = new ArrayList<>();

4.2 向ArrayList插入数据

Object类型的ArrayList添加数据时,所有数据都会自动向上转型为Object类型。我们将尝试添加多种类型的数据:

  • 基本数据类型需通过包装类转换
  • 其他类型(如StringList、自定义对象)可直接添加
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检索数据时,需要手动将其转换回原始类型。有两种主要方法:

  1. 使用instanceof关键字
  2. 使用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限制只允许StringInteger类型:

@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类型简单粗暴,但在实际开发中更推荐使用接口、父类或自定义包装类等类型安全的替代方案,避免运行时类型转换的踩坑风险。


原始标题:Create an ArrayList with Multiple Object Types | Baeldung