将Java Properties转换为HashMap

2023/06/07

1. 概述

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

在本教程中,我们将重点介绍将java.util.Properties转换为HashMap的各种方法。我们将使用普通Java、Lambda或外部库实现不同的方法来实现我们的目标。通过示例,我们将讨论每种解决方案的优缺点。

2. HashMap构造函数

在实现我们的第一个代码之前,让我们检查一下java.util.Properties的Javadoc。如我们所见,这个实用程序类继承自Hashtable,它也实现了Map接口。此外,Java包装了它的Reader和Writer类以直接处理String值。

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

假设我们已经正确加载了我们的属性,我们可以实现:

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

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

首先,根据继承图,我们需要将Properties转换为原始Map。此操作将强制发出第一个编译器警告,可以使用@SuppressWarnings(“rawtypes”)注解将其禁用。

之后,我们将原始Map转换为Map<String, String>,导致另一个编译器警告,可以使用@SupressWarnings(“unchecked”)抑制。

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

根据文档,Properties类具有强制使用String值的setProperty()getProperty()方法。但也有从Hashtable继承的put()putAll()方法,它们允许在我们的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());

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

3. Guava API

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

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

和以前一样,当我们完全确定Properties仅包含String值时,此方法可以正常工作。具有一些不合格的值会导致意想不到的行为:

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

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

Guava API不执行任何额外的映射。因此,它不允许我们转换这些Properties,从而引发异常。

在第一种情况下,由于无法通过Properties.getProperty()方法检索Integer值而抛出NullPointerException,因此被解释为null。第二个示例抛出与输入属性映射上出现的非字符串键相关的ClassCastException。

该解决方案为我们提供了更好的类型控制,并告知我们在转换过程中发生的违规行为

4. 自定义类型安全实现

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

4.1 使用for循环迭代

让我们实现一个简单的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;
}

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

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

4.2 Stream和Collectors API

我们甚至可以使用现代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
    ));
}

在这种情况下,我们使用的是Java 8 Stream收集器,没有显式HashMap构造。此方法实现与上一示例中介绍的完全相同的逻辑。

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

但是,我们可以在将值放入生成的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);

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

5. 总结

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

我们从类型转换解决方案开始,它可能是最快的转换,但也会带来编译器警告和潜在的类型安全错误。

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

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

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章