概述

在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上找到。