1. 概述
在Java中,我们经常使用如map这样的集合来存储键值对。
在这篇简短教程中,我们将探讨如何将Map<String, Object>
转换为Map<String, String>
。
2. 问题介绍
首先,我们创建一个Map<String, Object>
:
static final Map<String, Object> MAP1 = Maps.newHashMap();
static {
MAP1.put("K01", "GNU Linux");
MAP1.put("K02", "Mac OS");
MAP1.put("K03", "MS Windows");
}
如果转换为Map<String, String>
,结果应该如下所示:
static final Map<String, String> EXPECTED_MAP1 = Maps.newHashMap();
static {
EXPECTED_MAP1.put("K01", "GNU Linux");
EXPECTED_MAP1.put("K02", "Mac OS");
EXPECTED_MAP1.put("K03", "MS Windows");
}
我们可能会首先尝试使用HashMap(Map<? extends K, ? extends V> m)
构造函数完成转换。让我们试一试:
Map<String,String> result = new HashMap<String,String>(MAP1);
不幸的是,上面的代码无法编译:
no suitable constructor found for HashMap(java.util.Map<java.lang.String,java.lang.Object>)
在这篇教程中,我们将讨论解决这个问题的不同方法。
另外,虽然MAP1
的类型是Map<String, Object>
,但所有条目的值都是字符串。由于值的类型参数是Object
,输入映射可能包含值非字符串的条目:
static final Map<String, Object> MAP2 = Maps.newHashMap();
static {
MAP2.put("K01", "GNU Linux");
MAP2.put("K02", "Mac OS");
MAP2.put("K03", BigDecimal.ONE); // value is not a string
}
我们也会涵盖这种转换场景。
为了简化,我们将MAP1
和MAP2
作为输入,并使用单元测试断言来验证每个解决方案是否按预期工作。
3. 转换为Map
(不安全)
泛型类型是编译时特性。**在运行时,所有类型参数都会被擦除,所有的映射都具有相同的类型Map<Object, Object>
**。因此,我们可以将MAP1
转换为原始的Map
并将其赋值给Map<String, String>
变量:
Map<String, String> result = (Map) MAP1;
assertEquals(EXPECTED_MAP1, result);
这个技巧可行,尽管编译时会有一个“不安全转换”警告。
然而,这并不安全。例如,如果某些条目的值不是字符串,这种方法会隐藏问题:
Map<String, String> result2 = (Map) MAP2;
assertFalse(result2.get("K03") instanceof String);
如上测试所示,尽管result2
是Map<String, String>
类型,但映射中的一个值不是字符串。这可能导致进一步处理中的意外问题。
接下来,我们将看到如何实现安全转换。
4. 创建checkAndTransform()
方法
我们可以构建一个checkAndTransform()
方法来进行安全转换:
Map<String, String> checkAndTransform(Map<String, Object> inputMap) {
Map<String, String> result = new HashMap<>();
for (Map.Entry<String, Object> entry : inputMap.entrySet()) {
try {
result.put(entry.getKey(), (String) entry.getValue());
} catch (ClassCastException e) {
throw e; // or a required error handling
}
}
return result;
}
如上述方法所示,我们遍历输入映射中的条目,将每个条目的值转换为String
,并将键值对放入新的Map<String, String>
中。
让我们用MAP1
进行测试:
Map<String, String> result = checkAndTransform(MAP1);
assertEquals(EXPECTED_MAP1, result);
此外,一旦输入映射中包含无法转换为String
的值,ClassCastException
将被抛出。让我们将MAP2
传递给方法进行测试:
assertThrows(ClassCastException.class, () -> checkAndTransform(MAP2));
为了简洁,我们在catch
块中重新抛出异常。根据需求,我们可以在捕获ClassCastException
后执行适当的错误处理过程。
5. 将所有值转换为字符串
有时,即使映射中包含无法转换为String
的值,我们也不希望捕获任何异常。相反,对于这些“非字符串”值,我们希望取对象的String
表示形式作为值。例如,我们想将MAP2
转换为此映射:
static final Map<String, String> EXPECTED_MAP2_STRING_VALUES = Maps.newHashMap();
static {
EXPECTED_MAP2_STRING_VALUES.put("K01", "GNU Linux");
EXPECTED_MAP2_STRING_VALUES.put("K02", "Mac OS");
EXPECTED_MAP2_STRING_VALUES.put("K03", "1"); // string representation of BigDecimal.ONE
}
让我们使用流API来实现它:
Map<String, String> result = MAP1.entrySet()
.stream()
.collect(toMap(Map.Entry::getKey, e -> String.valueOf(e.getValue())));
assertEquals(EXPECTED_MAP1, result);
Map<String, String> result2 = MAP2.entrySet()
.stream()
.collect(toMap(Map.Entry::getKey, e -> String.valueOf(e.getValue())));
assertEquals(EXPECTED_MAP2_STRING_VALUES, result2);
值得一提的是,**我们使用String.valueOf()
而不是e.toString()
来避免潜在的NullPointerException
**。
6. 总结
在这篇文章中,我们学习了将Map<String, Object>
转换为Map<String, String>
的不同方式。
我们也讨论了当输入映射包含非字符串值的情况。
如往常一样,这里展示的所有代码片段都可以在GitHub上找到。