概述
在Java中,格式化并以美观可读的方式显示Map
的内容并不直接内置。我们需要自定义解决方案来实现这一目标。本教程将引导我们如何达成这个目标,我们会探讨使用标准JDK和外部库的不同方法,根据偏好和细节需求进行选择。
1. 创建Map
在继续之前,让我们创建一个Map
实例供后续操作:
Map<String, Object> map = Map.of(
"one", 1,
"two", 2,
"inner", Map.of(
"ten", 10,
"eleven", 11
)
);
值得注意的是,我们在此示例中添加了一个嵌套的内部Map
。
2. 使用核心Java
Java通过内置的toString()
方法可以打印Map
,但输出较为简单,键值对以逗号分隔,显示在一行上:
{one=1, two=2, inner={eleven=11, ten=10}}
对于简单的Map
或调试时,这已经足够。但如果我们想要漂亮的打印效果,就需要自定义方法。
2.1. 使用for-each
循环
当我们需要遍历所有元素时,可以使用for-each
循环:
for (Map.Entry<?, ?> entry : map.entrySet()) {
System.out.printf("%-15s : %s%n", entry.getKey(), entry.getValue());
}
这将输出:
one : 1
two : 2
inner : {ten=10, eleven=11}
输出看起来更美观,但嵌套的Map
还未格式化,因此需要手动处理复杂结构。
为了格式化嵌套项,我们可以实现一个递归辅助函数,并添加左填充参数:
void printMap(int leftPadding, Map<?, ?> map) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
System.out.printf("%-15s :%n", entry.getKey());
printMap(leftPadding + 4, (Map<?, ?>) entry.getValue());
}
else {
System.out.printf("%" + (leftPadding > 0 ? leftPadding : "") + "s" // adding padding
+ "%-15s : %s%n",
"", entry.getKey(), entry.getValue());
}
}
}
现在,如果调用printMap(0, map)
,结果如下:
one : 1
two : 2
inner :
ten : 10
eleven : 11
通过自定义解决方案,我们始终能完全控制地图的打印。我们可以利用内置的格式化工具,如Formatter
类、String.format()
或System.out.printf()
来自定义输出。然而,对于多类型或嵌套结构,自定义函数可能会稍显复杂。
2.2. 使用Stream
在Java中,几乎任何for-each
循环都可以用Stream
替代。我们可以用一行代码来打印和格式化Map
:
map.forEach((k, v) -> System.out.printf("%-15s : %s%n", k, v));
如果我们想要更多控制,可以扩展流并使用map()
或Collectors.joining()
函数:
System.out.println(MAP.entrySet().stream()
.map(entry -> String.format("%-15s : %s", entry.getKey(), entry.getValue()))
.collect(Collectors.joining("\n")));
在这两个例子中,我们都会得到:
one : 1
two : 2
inner : {ten=10, eleven=11}
再次强调,这种方法提供了更强的格式控制,特别适合处理简单类型。但我们必须记住,仍需手动处理复杂结构,就像之前一样。
3. 外部库
对于不包含复杂映射的Map
,自定义的漂亮打印功能可能是更好的选择。额外的映射会使代码变得更复杂,不值得自己实现。让我们看看外部库提供的解决方案。
3.1. Jackson
当我们比较JSON与Map
时,会发现它们有许多相似之处。两者都使用键值对表示条目。
首先,让我们查看流行的JSON库Jackson,通过在pom.xml
中添加依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
Jackson提供了ObjectMapper
类,不仅用于处理JSON,也可用于处理标准Map
:
String json = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(map);
因此,Jackson API可以自动漂亮地打印我们的Map
:
{
"one" : 1,
"two" : 2,
"inner" : {
"ten" : 10,
"eleven" : 11
}
}
这个解决方案自动处理了嵌套的Map
,比之前的更简洁。但遗憾的是,我们对映射的控制度有限。
3.2. Gson
当然,其他JSON库也支持漂亮地打印带有嵌套值的Map
。让我们通过添加最新版本的依赖来检查Gson:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
就像之前一样,我们配置一个GsonBuilder
:
String json = new GsonBuilder().setPrettyPrinting().create().toJson(MAP);
现在输出如下:
{
"one": 1,
"two": 2,
"inner": {
"ten": 10,
"eleven": 11
}
}
值得注意的是,格式稍微不同(键后面没有空格),但同样**方便地打印包含嵌套值的Map
**。
3.3. Apache Commons Collections
既然知道许多JSON库支持JSON和Map
的漂亮打印,让我们探索非JSON库提供的其他解决方案。让我们通过添加Apache Commons Collections的依赖到项目中:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.5.0-M2</version>
</dependency>
该库提供了MapUtils
类。让我们使用它来打印Map
:
MapUtils.debugPrint(System.out, "map", map);
结果将呈现为:
map =
{
one = 1 java.lang.Integer
two = 2 java.lang.Integer
inner =
{
ten = 10 java.lang.Integer
eleven = 11 java.lang.Integer
} java.util.HashMap
} java.util.HashMap
我们刚刚使用了debugPrint()
方法来格式化Map
。如果我们想省略值的类名,可以使用verbosePrint()
方法。
3.4. Google Guava
最后,让我们看看Google Guava库提供的方法。首先更新pom.xml
:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
</dependency>
要打印Map
,我们可以使用Joiner
类:
String mapStr = Joiner.on(",\n").withKeyValueSeparator("=").join(map);
现在查看结果,我们会得到:
one=1,
two=2,
inner={ten=10, eleven=11}
不幸的是,这种方法无法处理嵌套条目,但对于单层结构,它工作得很好。
4. 总结
在这篇文章中,我们学习了在Java中使用不同方法漂亮打印Map
的方法。我们了解到,使用内置的toString()
方法打印可能会产生难以阅读的一行字符串。
我们首先通过标准Java API实现自定义的漂亮打印方法,特别是使用for-each
循环、流和格式器。这种做法适用于简单的非嵌套Map
,或者我们希望完全控制映射。
接下来,我们研究了由Jackson、Gson、Apache Commons Collections和Guava等外部库提供的解决方案。外部API总是比自定义解决方案更简单,但我们对预定义打印格式的控制程度较低。
如往常一样,文章附带的源代码可在GitHub上找到。