1. 概述

HashMap 是 Java 中用于存储键值对的核心数据结构。本文将探讨如何在同一个 HashMap 中存储不同类型的值,并提供两种实现方案,同时分析各自的优缺点。

2. 问题引入

自 Java 引入泛型后,我们通常这样使用 HashMap

Map<String, Integer> numberByName = new HashMap<>();

这种写法限制了键值对只能为 StringInteger,确保了类型安全。但有时我们需要在同一个 Map 中存储多种类型的数据,比如同时存放 IntegerFloatBigDecimal 值。

为演示解决方案,我们准备三种不同类型的对象:

Integer intValue = 777;
int[] intArray = new int[]{2, 3, 5, 7, 11, 13};
Instant instant = Instant.now();

接下来我们将探索两种方法,将这些对象存入 Map,并正确读取使用它们。

3. 使用 Map<String, Object>

3.1 存入数据

Object 是 Java 中所有类型的超类,因此 Map<String, Object> 能接受任何类型的值:

Map<String, Object> rawMap = new HashMap<>();
rawMap.put("E1 (Integer)", intValue);
rawMap.put("E2 (IntArray)", intArray);
rawMap.put("E3 (Instant)", instant);

3.2 使用数据

存入后值的具体类型会丢失,使用前必须先检查并转换类型:

rawMap.forEach((k, v) -> {
    if (v instanceof Integer) {
        Integer theV = (Integer) v;
        System.out.println(k + " -> "
          + String.format("The value is a %s integer: %d", theV > 0 ? "positive" : "negative", theV));
    } else if (v instanceof int[]) {
        int[] theV = (int[]) v;
        System.out.println(k + " -> "
          + String.format("The value is an array of %d integers: %s", theV.length, Arrays.toString(theV)));
    } else if (v instanceof Instant) {
        Instant theV = (Instant) v;
        System.out.println(k + " -> "
          + String.format("The value is an instant: %s", FORMATTER.format(theV)));
    } else {
        throw new IllegalStateException("Unknown Type Found.");
    }
});

执行结果:

E1 (Integer) -> The value is a positive integer: 777
E2 (IntArray) -> The value is an array of 6 integers: [2, 3, 5, 7, 11, 13]
E3 (Instant) -> The value is an instant: 2021-11-23 21:48:02

3.3 缺点分析

这种方案存在三个明显问题:

  1. 代码可读性差:当支持的类型增多时,if-else 链会变成臃肿的代码块
  2. 类型判断失效:当类型存在继承关系时(如 IntegerNumber),instanceof 无法准确区分具体类型
  3. 类型安全缺失:必须处理未知类型导致的异常,增加维护成本

踩坑提醒:当处理继承层级复杂的类型时,简单粗暴的 instanceof 可能会给你挖坑。

4. 创建统一超类型

4.1 数据模型设计

首先定义一个接口作为所有类型的超类型:

public interface DynamicTypeValue {
    String valueDescription();
}

然后为每种类型创建包装类实现该接口。以 Integer 为例:

public class IntegerTypeValue implements DynamicTypeValue {
    private Integer value;

    public IntegerTypeValue(Integer value) {
        this.value = value;
    }

    @Override
    public String valueDescription() {
        if(value == null){
            return "The value is null.";
        }
        return String.format("The value is a %s integer: %d", value > 0 ? "positive" : "negative", value);
    }
}

类似地创建其他类型的包装类:

public class IntArrayTypeValue implements DynamicTypeValue {
    private int[] value;

    public IntArrayTypeValue(int[] value) { ... }

    @Override
    public String valueDescription() {
        // null handling omitted
        return String.format("The value is an array of %d integers: %s", value.length, Arrays.toString(value));
    }
}
public class InstantTypeValue implements DynamicTypeValue {
    private static DateTimeFormatter FORMATTER = ...

    private Instant value;

    public InstantTypeValue(Instant value) { ... }

    @Override
    public String valueDescription() {
        // null handling omitted
        return String.format("The value is an instant: %s", FORMATTER.format(value));
    }
}

4.2 数据存取操作

声明 Map 时使用统一超类型:

Map<String, DynamicTypeValue> theMap = new HashMap<>();
theMap.put("E1 (Integer)", new IntegerTypeValue(intValue));
theMap.put("E2 (IntArray)", new IntArrayTypeValue(intArray));
theMap.put("E3 (Instant)", new InstantTypeValue(instant));

读取数据时无需类型转换:

theMap.forEach((k, v) -> System.out.println(k + " -> " + v.valueDescription()));

输出结果:

E1 (Integer) -> The value is a positive integer: 777
E2 (IntArray) -> The value is an array of 5 integers: [2, 3, 5, 7, 11]
E3 (Instant) -> The value is an instant: 2021-11-23 22:32:43

方案优势

  1. 代码简洁可读,消除冗长的类型判断
  2. 完美处理继承关系类型
  3. 编译时类型安全,无需处理未知类型异常

5. 总结

本文探讨了在 Java HashMap 中存储多类型值的两种方案:

方案 优点 缺点
Map<String, Object> 实现简单 类型不安全,代码可维护性差
统一超类型 类型安全,代码优雅 需要创建包装类

推荐在需要存储多类型值的场景下优先采用统一超类型方案,虽然前期需要定义包装类,但长期维护成本更低。

完整示例代码可参考:GitHub 仓库


原始标题:Java HashMap With Different Value Types

» 下一篇: JMX 端口