Converting Java Properties to HashMap – 将Java属性转换为HashMap

最后修改: 2021年 2月 28日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Many developers decide to store application parameters outside the source code. One of the ways to do so in Java is to use an external configuration file and read them via the java.util.Properties class.

许多开发者决定在源代码之外存储应用程序的参数。在Java中,这样做的方法之一是使用外部配置文件,并通过java.util.Properties类读取它们。

In this tutorial, we’ll focus on various approaches to convert java.util.Properties into a HashMap<String, String>. We’ll implement different methods to achieve our goal, using plain Java, lambdas, or external libraries. Through examples, we’ll discuss the pros and cons of each solution.

在本教程中,我们将重点讨论java.util.Properties转换为HashMap<String, String>a>的各种方法。我们将使用普通的Java、lambdas或外部库,实现不同的方法来实现我们的目标。通过实例,我们将讨论每种解决方案的利弊。

2. HashMap Constructor

2、HashMap生成器

Before we implement our first code, let’s check the Javadoc for java.util.Properties. As we see, this utility class inherits from Hashtable<Object, Object>, which also implements the Map interface. Moreover, Java wraps its Reader and Writer classes to work directly on String values.

在我们实现第一段代码之前,让我们查看Javadoc中的java.util.Properties。我们看到,这个实用类继承自Hashtable<Object, Object>,它还实现了Map接口。此外,Java封装了它的ReaderWriter类,以直接处理String值。

According to that information, we can convert Properties into HashMap<String, String> using typecasting and constructor calls.

根据这些信息,我们可以使用类型转换和构造函数调用将Properties转换成HashMap<String, String>

Assuming that we’ve loaded our Properties correctly, we can implement:

假设我们已经正确加载了我们的Properties,我们可以实现。

public static HashMap<String, String> typeCastConvert(Properties prop) {
    Map step1 = prop;
    Map<String, String> step2 = (Map<String, String>) step1;
    return new HashMap<>(step2);
}

Here, we implement our conversion in three simple steps.

在这里,我们通过三个简单的步骤实现我们的转换。

First, according to the inheritance graph, we need to cast our Properties into a raw Map. This action will force the first compiler warning, which can be disabled by using the @SuppressWarnings(“rawtypes”) annotation.

首先,根据继承图,我们需要将我们的Properties投到一个原始Map中。这个动作将强制执行第一个编译器警告,可以通过使用@SuppressWarnings(“rawtypes”)注解来禁用。

After that, we cast our raw Map into Map<String, String>, causing another compiler warning, which can be omitted by using @SupressWarnings(“unchecked”).

之后,我们将我们的原始Map铸成Map<String, String>,引起另一个编译器警告,可以通过使用@SupressWarnings(“unchecked”)省略。

Finally, we build our HashMap using the copy constructor. This is the fastest way to convert our Properties, but this solution also has a big disadvantage related to type safety: Our Properties might be compromised and modified before the conversion.

最后,我们使用复制构造函数来构建我们的HashMap。这是转换我们的Properties的最快方法,但是这个解决方案也有一个与类型安全有关的很大的缺点。我们的Properties可能会在转换之前被破坏和修改。

According to the documentation, the Properties class has the setProperty() and getProperty() methods that force the use of String values. But also there are put() and putAll() methods inherited from Hashtable that allow using any type as keys or values in our Properties:

根据文档,Properties类有setProperty()getProperty() 方法,强制使用 String值。但是也有put()putAll()方法继承自Hashtable,允许使用任何类型作为我们Properties>中的键或值。

properties.put("property4", 456);
properties.put(5, 10.11);

HashMap<String, String> hMap = typeCastConvert(properties);
assertThrows(ClassCastException.class, () -> {
    String s = hMap.get("property4");
});
assertEquals(Integer.class, ((Object) hMap.get("property4")).getClass());

assertThrows(ClassCastException.class, () -> {
    String s = hMap.get(5);
});
assertEquals(Double.class, ((Object) hMap.get(5)).getClass());

As we can see, our conversion executes without any error, but not all elements in the HashMap are strings. So, even if this method looks the easiest, we must keep in mind some safety-related checks in the future.

正如我们所看到的,我们的转换执行起来没有任何错误,但是HashMap中的元素并非都是字符串因此,即使这个方法看起来最简单,我们也必须在未来牢记一些与安全相关的检查

3. The Guava API

3.Guava API

If we can use third-party libraries, the Google Guava API comes in handy. This library delivers a static Maps.fromProperties() method, which does almost everything for us. According to the documentation, this call returns an ImmutableMap, so if we want to have the HashMap, we can use:

如果我们可以使用第三方库,那么Google Guava API就会派上用场。这个库提供了一个静态的Maps.fromProperties()方法,它几乎为我们做了一切。根据文档,这个调用返回一个ImmutableMap,所以如果我们想拥有HashMap,我们可以使用。

public HashMap<String, String> guavaConvert(Properties prop) {
    return Maps.newHashMap(Maps.fromProperties(prop));
}

As previously, this method works fine when we’re completely sure that the Properties contain only String values. Having some non-conforming values will lead to unexpected behavior:

如前所述,该方法当我们完全确定Properties只包含String值时,可以正常工作。如果有一些不符合要求的值,将导致意外的行为。

properties.put("property4", 456);
assertThrows(NullPointerException.class, 
    () -> PropertiesToHashMapConverter.guavaConvert(properties));

properties.put(5, 10.11);
assertThrows(ClassCastException.class, 
    () -> PropertiesToHashMapConverter.guavaConvert(properties));

The Guava API doesn’t perform any additional mappings. As a result, it doesn’t allow us to convert those Properties, throwing exceptions.

Guava API并没有进行任何额外的映射。因此,它不允许我们转换这些Properties,抛出异常。

In the first case, the NullPointerException is thrown due to the Integer value, which cannot be retrieved by the Properties.getProperty() method and, as a result, is interpreted as null. The second example throws the ClassCastException related to the non-string key occurring on the input property map.

在第一个例子中,由于Integer值,抛出了NullPointerException,它不能被Properties.getProperty()方法检索到,因此,被解释为null。第二个例子抛出的ClassCastException与输入属性图上出现的non-string键有关。

This solution gives us better type control and also informs us of violations that occur during the conversion process.

这个解决方案给我们提供了更好的类型控制,同时也通知我们在转换过程中出现的违规行为

4. Custom Type Safety Implementation

4.自定义类型安全的实现

It’s now time to resolve our type of safety problems from the previous examples. As we know, the Properties class implements methods inherited from the Map interface. We’ll use one of the possible ways of iterating over a Map to implement a proper solution and enrich it with type checks.

现在是时候解决我们在前面例子中的安全类型问题了。正如我们所知,Properties类实现了从Map接口继承的方法。我们将使用Map上迭代的一种可能的方法来实现一个适当的解决方案,并用类型检查来充实它。

4.1. Iteration Using for Loop

4.1.使用for循环进行迭代

Let’s implement a simple for-loop:

我们来实现一个简单的for循环。

public HashMap<String, String> loopConvert(Properties prop) {
    HashMap<String, String> retMap = new HashMap<>();
    for (Map.Entry<Object, Object> entry : prop.entrySet()) {
        retMap.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
    }
    return retMap;
}

In this method, we iterate over the Properties in the same way as we do for a typical Map. As a result, we have one-by-one access to every single key-pair value represented by the Map.Entry class.

在这个方法中,我们以与典型的Map相同的方式遍历Properties。因此,我们可以逐一访问Map.Entry类所代表的每个键对值。

Before putting values in a returned HashMap, we can perform additional checks, so we decide to use the String.valueOf() method.

在将值放入返回的HashMap之前,我们可以进行额外的检查,所以我们决定使用String.valueOf()方法。

4.2. Stream and Collectors API

4.2.StreamCollectorsAPI

We can even refactor our method using the modern Java 8 way:

我们甚至可以用现代的Java 8方式重构我们的方法。

public HashMap<String, String> streamConvert(Properties prop) {
    return prop.entrySet().stream().collect(
      Collectors.toMap(
        e -> String.valueOf(e.getKey()),
        e -> String.valueOf(e.getValue()),
        (prev, next) -> next, HashMap::new
    ));
}

In this case, we’re using Java 8 Stream Collectors without explicit HashMap construction. This method implements exactly the same logic introduced in the previous example.

在这种情况下,我们使用Java 8 Stream Collectors,而没有明确的HashMap构造。这个方法实现的逻辑与前面例子中介绍的完全相同。

Both solutions are slightly more complex because they require some custom implementation that the typecasting and Guava examples don’t.

这两种解决方案都稍显复杂,因为它们需要一些自定义的实现,而类型转换和Guava的例子则不需要。

However, we have access to the values before putting them on the resulting HashMap, so we can implement additional checks or mappings:

然而,在将这些值放到所产生的HashMap上之前,我们可以访问这些值因此我们可以实现额外的检查或映射

properties.put("property4", 456);
properties.put(5, 10.11);

HashMap<String, String> hMap1 = loopConvert(properties);
HashMap<String, String> hMap2 = streamConvert(properties);

assertDoesNotThrow(() -> {
    String s1 = hMap1.get("property4");
    String s2 = hMap2.get("property4");
});
assertEquals("456", hMap1.get("property4"));
assertEquals("456", hMap2.get("property4"));

assertDoesNotThrow(() -> {
    String s1 = hMap1.get("property4");
    String s2 = hMap2.get("property4");
});
assertEquals("10.11", hMap1.get("5"));
assertEquals("10.11", hMap2.get("5"));

assertEquals(hMap2, hMap1);

As we can see, we solved our problems related to non-string values. Using this approach, we can manually adjust the mapping logic to achieve proper implementation.

我们可以看到,我们解决了与非字符串值有关的问题。使用这种方法,我们可以手动调整映射逻辑以实现正确的实现。

5. Conclusion

5.总结

In this tutorial, we checked different approaches to convert java.util.Properties into a HashMap<String, String>.

在本教程中,我们检查了将java.util.Properties转换为HashMap<String, String>的不同方法。

We started with a typecasting solution that is perhaps the fastest conversion but also brings compiler warnings and potential type safety errors.

我们从一个类型转换的解决方案开始,这也许是最快的转换,但也带来了编译器警告和潜在的类型安全错误

Then we took a look at a solution using Guava API, which resolves compiler warnings and brings some improvements for handling errors.

然后我们看了一个使用Guava API的解决方案,它解决了编译器的警告,并为处理错误带来了一些改进。

Finally, we implemented our custom methods, which deal with type safety errors and give us the most control.

最后,我们实现了我们的自定义方法,这些方法处理类型安全错误,给我们带来了最大的控制。

All code snippets from this tutorial are available over on GitHub.

本教程的所有代码片段都可以在GitHub上找到