1. 概述
HashMap
是 Java 中用于存储键值对的核心数据结构。本文将探讨如何在同一个 HashMap
中存储不同类型的值,并提供两种实现方案,同时分析各自的优缺点。
2. 问题引入
自 Java 引入泛型后,我们通常这样使用 HashMap
:
Map<String, Integer> numberByName = new HashMap<>();
这种写法限制了键值对只能为 String
和 Integer
,确保了类型安全。但有时我们需要在同一个 Map
中存储多种类型的数据,比如同时存放 Integer
、Float
和 BigDecimal
值。
为演示解决方案,我们准备三种不同类型的对象:
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 缺点分析
这种方案存在三个明显问题:
- 代码可读性差:当支持的类型增多时,
if-else
链会变成臃肿的代码块 - 类型判断失效:当类型存在继承关系时(如
Integer
和Number
),instanceof
无法准确区分具体类型 - 类型安全缺失:必须处理未知类型导致的异常,增加维护成本
踩坑提醒:当处理继承层级复杂的类型时,简单粗暴的
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
方案优势:
- 代码简洁可读,消除冗长的类型判断
- 完美处理继承关系类型
- 编译时类型安全,无需处理未知类型异常
5. 总结
本文探讨了在 Java HashMap
中存储多类型值的两种方案:
方案 | 优点 | 缺点 |
---|---|---|
Map<String, Object> |
实现简单 | 类型不安全,代码可维护性差 |
统一超类型 | 类型安全,代码优雅 | 需要创建包装类 |
推荐在需要存储多类型值的场景下优先采用统一超类型方案,虽然前期需要定义包装类,但长期维护成本更低。
完整示例代码可参考:GitHub 仓库