1. Overview
1.概述
A HashMap stores key-value mappings. In this tutorial, we’ll discuss how to store values of different types in a HashMap.
HashMap存储键值映射。在本教程中,我们将讨论如何在HashMap中存储不同类型的值。
2. Introduction to the Problem
2.对问题的介绍
Since the introduction of Java Generics, we’ve typically used HashMap in a generic way – for example:
自从引入Java泛型后,我们通常以泛型的方式使用HashMap – 例如。
Map<String, Integer> numberByName = new HashMap<>();
In this case, we can only put String and Integer data as key-value pairs into the map numberByName. That’s good, as it ensures type safety. For example, if we attempt to put a Float object into the Map, we’ll get the “incompatible types” compilation error.
在这种情况下,我们只能将String和Integer数据作为键值对放入map numberByName。这很好,因为它确保了类型安全。例如,如果我们试图将Float对象放入Map,我们会得到 “不兼容类型 “的编译错误。
However, sometimes, we would like to put data of different types into a Map. For example, we want the numberByName map to store Float and BigDecimal objects as values as well.
然而,有时,我们希望将不同类型的数据放入Map。例如,我们希望numberByName地图也能存储Float和BigDecimal对象作为值。
Before discussing how to achieve that, let’s create an example problem to make the demonstration and explanation easier. Let’s say we have three objects of different types:
在讨论如何实现这一目标之前,让我们创造一个例子问题,以使演示和解释更加容易。假设我们有三个不同类型的对象。
Integer intValue = 777;
int[] intArray = new int[]{2, 3, 5, 7, 11, 13};
Instant instant = Instant.now();
As we can see, the three types are entirely different. So first, we’ll try to put these three objects in a HashMap. To make it simple, we’ll use String values as keys.
我们可以看到,这三种类型是完全不同的。所以首先,我们将尝试把这三个对象放在一个HashMap中。为了简单起见,我们将使用String值作为键。
Of course, at some point, we need to read the data out from the Map and use the data. Therefore, we’ll walk through the entries in the HashMap, and for each entry, we print the value with some description.
当然,在某些时候,我们需要从Map中读出数据并使用这些数据。因此,我们将浏览HashMap中的条目,对于每个条目,我们将打印出带有一些描述的值。
So, let’s see how we can achieve that.
因此,让我们看看我们如何实现这一目标。
3. Using Map<String, Object>
3.使用Map<String, Object>
We know that in Java, Object is the supertype of all types. Therefore, if we declare a Map as Map<String, Object>, it should accept values of any type.
我们知道,在Java中,Object是所有类型的超类型。因此,如果我们将一个Map声明为Map<String, Object>,它应该接受任何类型的值。
Next, let’s see if this approach meets our requirements.
接下来,让我们看看这种方法是否符合我们的要求。
3.1. Putting Data Into the Map
3.1.将数据放入Map中
As we’ve mentioned earlier, a Map<String, Object> allows us to put values of any type in it:
正如我们前面提到的,Map<String, Object>允许我们将任何类型的值放入其中。
Map<String, Object> rawMap = new HashMap<>();
rawMap.put("E1 (Integer)", intValue);
rawMap.put("E2 (IntArray)", intArray);
rawMap.put("E3 (Instant)", instant);
It’s pretty straightforward. Next, let’s visit the entries in the Map and print the value and description.
这是很直接的。接下来,让我们访问Map中的条目,并打印其值和描述。
3.2. Using the Data
3.2.使用数据
After we put a value in the Map<String, Object>, we’ve lost the value’s concrete type. Therefore, we need to check and cast the value to the proper type before using the data. For instance, we can use the instanceof operator to verify a value’s type:
当我们把一个值放到Map<String, Object>中后,我们已经失去了这个值的具体类型。因此,在使用数据之前,我们需要检查并将该值转换为适当的类型。例如,我们可以使用instanceof操作符来验证一个值的类型。
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.");
}
});
If we execute the code above, we’ll see the output:
如果我们执行上面的代码,我们会看到输出。
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
This approach works as we expected.
这种方法如我们所预期的那样运作。
However, it has some disadvantages. Next, let’s take a closer look at them.
然而,它也有一些缺点。接下来,让我们仔细看看它们。
3.3. Disadvantages
3.3.劣势
First, if we’ve planned to let the map support relatively more different types, the multiple if-else statements will become a large code block and make the code difficult to read.
首先,如果我们已经计划让地图支持相对更多的不同类型,多个if-else语句将成为一个大的代码块,使代码难以阅读。
Moreover, if the types we want to use contain inheritance relationships, the instanceof check may fail.
此外,如果我们想要使用的类型包含继承关系,instanceof检查可能会失败。
For example, if we put a java.lang.Integer intValue and a java.lang.Number numberValue in the map, we cannot distinguish them using the instanceof operator. This is because both (intValue instanceof Integer) and (intValue instanceof Number) return true.
例如,如果我们把一个java.lang.Integer intValue和一个java.lang.Number numberValue放入map中,我们不能用instanceof操作符来区分它们。这是因为(intValue instanceof Integer)和(intValue instanceof Number)都返回true。
Therefore, we must add extra checks to determine a value’s concrete type. But, of course, this will make the code difficult to read.
因此,我们必须增加额外的检查来确定一个值的具体类型。但是,当然,这将使代码难以阅读。
Finally, since our map accepts values of any type, we’ve lost the type safety. That is to say, we have to handle the exception when unexpected types are encountered.
最后,由于我们的map接受任何类型的值,我们已经失去了类型安全。也就是说,当遇到意外的类型时,我们必须要处理异常。
A question may come up: Is there a way to accept different types’ data and preserve the type safety?
一个问题可能会出现。有没有一种方法可以接受不同类型的数据并保持类型安全?
So next, we’ll address another approach to solve the problem.
所以接下来,我们将讨论另一种解决问题的方法。
4. Creating a Supertype for All Required Types
4.为所有需要的类型创建一个超级类型
In this section, we’ll introduce a supertype to preserve type safety.
在这一节中,我们将引入一个超类型来保持类型安全。
4.1. Data Model
4.1.数据模型
First, we create an interface DynamicTypeValue:
首先,我们创建一个接口DynamicTypeValue。
public interface DynamicTypeValue {
String valueDescription();
}
This interface will be the supertype of all types we expect the map to support. It can also contain some common operations. For example, we’ve defined a method valueDescription.
这个接口将是我们期望地图支持的所有类型的超类型。它也可以包含一些常见的操作。例如,我们定义了一个方法valueDescription。
Then, we create a class for each concrete type to wrap the value and implement the interface we’ve created. For example, we can create an IntegerTypeValue class for the Integer type:
然后,我们为每个具体的类型创建一个类,以包裹该值并实现我们所创建的接口。例如,我们可以为IntegerTypeValue类型创建一个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);
}
}
Similarly, let’s create classes for the other two types:
同样地,让我们为其他两种类型创建类。
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));
}
}
If we need to support more types, we just add corresponding classes.
如果我们需要支持更多的类型,我们只需添加相应的类。
Next, let’s look at how to use the data model above to store and use different types’ values in a map.
接下来,让我们看看如何使用上面的数据模型来存储和使用地图中不同类型的值。
4.2. Putting and Using the Data in the Map
4.2.在地图中放置和使用数据
First, let’s see how to declare the Map and put various types’ data in it:
首先,让我们看看如何声明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));
As we can see, we’ve declared the map as Map<String, DynamicTypeValue> so that the type safety is guaranteed: Only data with the DynamicTypeValue type are allowed to be put into the map.
正如我们所看到的,我们已经将地图声明为Map<String, DynamicTypeValue> ,这样就可以保证类型安全。只有具有DynamicTypeValue类型的数据才允许被放入map中。
When we add data to the map, we instantiate the corresponding class we’ve created.
当我们向地图添加数据时,我们会实例化我们所创建的相应类。
When we use the data, type checking and casting are not required:
当我们使用这些数据时,类型检查和铸造是不需要的。
theMap.forEach((k, v) -> System.out.println(k + " -> " + v.valueDescription()));
If we run the code, it’ll print:
如果我们运行这段代码,它就会打印出来。
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
As we can see, the code for this approach is clean and much easier to read.
我们可以看到,这种方法的代码很干净,而且更容易阅读。
Further, since we create a wrapper class for every type we need to support, types with inheritance relationships won’t lead to any problem.
此外,由于我们为每一个需要支持的类型都创建了一个封装类,具有继承关系的类型不会导致任何问题。
Thanks to the type safety, we don’t need to handle the error case of facing the data of unexpected types.
由于类型安全,我们不需要处理面对意外类型数据的错误情况。
5. Conclusion
5.总结
In this article, we’ve discussed how to make a Java HashMap support different types’ value data.
在这篇文章中,我们讨论了如何使JavaHashMap支持不同类型的值数据。
Also, we’ve addressed two approaches to achieve it through examples.
另外,我们还通过实例解决了两种实现的方法。
As always, the source code that accompanies the article is available over on GitHub.
像往常一样,伴随着文章的源代码可以在GitHub上获得。