1. 简介
很多开发者倾向于将应用配置参数放在源码之外,便于维护和部署。在 Java 中,一种常见的做法是使用外部配置文件,并通过 java.util.Properties
类来读取这些配置。
本篇文章将聚焦于**如何将 java.util.Properties
转换为 HashMap<String, String>
**,我们会介绍几种不同的实现方式:纯 Java 实现、使用 Lambda 表达式的方式,以及借助第三方库(如 Guava)的实现。每种方式我们都会结合示例代码分析其优劣。
2. 使用 HashMap 构造函数转换
在开始编码前,我们先来看看 Properties 的 Javadoc。可以看到,Properties
类继承自 Hashtable<Object, Object>
,同时也实现了 Map
接口。而且 Java 为 Properties
提供了专门处理字符串的 setProperty()
和 getProperty()
方法。
基于这个信息,我们可以直接使用类型转换和构造函数的方式完成转换:
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>
,同样会有类型未检查警告(可用@SuppressWarnings("unchecked")
处理); - 最后通过拷贝构造函数创建
HashMap
。
✅ 优点:这是最快的转换方式。
❌ 缺点:存在类型安全隐患,如果 Properties
中混入了非 String
类型的 key 或 value,后续使用时可能抛出 ClassCastException
。
比如:
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 提供了一个便捷的方法:Maps.fromProperties()
。它会返回一个 ImmutableMap<String, String>
,我们可以再包装为 HashMap
:
public HashMap<String, String> guavaConvert(Properties prop) {
return Maps.newHashMap(Maps.fromProperties(prop));
}
✅ 优点:类型安全更好,转换过程中如果遇到非字符串的 key 或 value 会直接抛出异常,提前暴露问题。
❌ 缺点:对非法值容忍度低,容易抛异常。
示例:
properties.put("property4", 456);
assertThrows(NullPointerException.class,
() -> PropertiesToHashMapConverter.guavaConvert(properties));
properties.put(5, 10.11);
assertThrows(ClassCastException.class,
() -> PropertiesToHashMapConverter.guavaConvert(properties));
第一个异常是由于 Integer
类型值无法通过 getProperty()
获取导致的 NullPointerException
;第二个则是 key 类型不合法导致的 ClassCastException
。
总的来说,Guava 的方式更安全但也更严格,适用于对配置数据类型有严格控制的场景。
4. 自定义类型安全实现
前面两种方式都存在一定的类型安全隐患或限制。现在我们来实现一个自定义的安全转换方法,手动控制类型转换逻辑。
4.1. 使用 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;
}
这种方式通过遍历 Properties
的 entrySet()
,对每个 key 和 value 使用 String.valueOf()
转换为字符串。
✅ 优点:类型安全可控,可扩展性强。
⚠️ 缺点:需要手动编写循环逻辑。
4.2. 使用 Stream + Collectors
Java 8 引入了 Stream API,我们可以用更现代的方式重写上述逻辑:
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
));
}
这段代码使用了 Collectors.toMap()
方法,逻辑与上面的 for 循环一致,但更简洁。
✅ 优点:代码更现代、可读性强,类型安全可控。
⚠️ 缺点:相比类型转换和 Guava,性能略低,但通常可以忽略。
测试示例:
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("5");
String s2 = hMap2.get("5");
});
assertEquals("10.11", hMap1.get("5"));
assertEquals("10.11", hMap2.get("5"));
assertEquals(hMap2, hMap1);
可以看到,两种方式都能正确处理非字符串类型的 key 和 value,最终都转换为字符串,且结果一致。
5. 总结
本文介绍了几种将 java.util.Properties
转换为 HashMap<String, String>
的常见方式:
方法 | 性能 | 类型安全 | 可控性 |
---|---|---|---|
类型转换(Type Casting) | ✅ 最快 | ❌ 有风险 | ❌ 无控制 |
Guava API | ✅ 安全 | ✅ 较好 | ⚠️ 遇非法值抛异常 |
自定义实现(for / stream) | ⚠️ 一般 | ✅ 安全 | ✅ 高 |
简单粗暴的类型转换虽然快,但容易埋坑;Guava 虽然安全但不灵活;自定义实现最稳妥,适用于对类型安全有严格要求的场景。选择哪种方式,取决于你对类型安全、性能和可维护性的权衡。