在Java中漂亮地打印一个Map

159

我正在寻找一种漂亮的方式来打印一个Map

map.toString() 给了我这样的结果: {key1=value1, key2=value2, key3=value3}

我希望在我的映射条目值中拥有更多自由,并寻找类似于这样的东西:key1="value1", key2="value2", key3="value3"

我写了这个小代码:

StringBuilder sb = new StringBuilder();
Iterator<Entry<String, String>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
    Entry<String, String> entry = iter.next();
    sb.append(entry.getKey());
    sb.append('=').append('"');
    sb.append(entry.getValue());
    sb.append('"');
    if (iter.hasNext()) {
        sb.append(',').append(' ');
    }
}
return sb.toString();

但我相信有更优雅、更简洁的方法来实现这个。


2
可能是Java中的Map转String的重复问题,因为这里请求的格式和System.out.println的格式非常接近。如果您想要自定义内容,那么这就归结为“如何在Java中迭代Map”,这肯定有很多其他答案。 - Ciro Santilli OurBigBook.com
16个回答

323
Arrays.toString(map.entrySet().toArray())

14
如果你有Map<String, Map<String,double[]>>这样的数据结构,那么你会得到这样的字符串:[test={test=[D@3995ebd8, 2=[D@26fa5067, 3=[D@1d956d37, 4=[D@2cead81, 5=[D@14934d2b}...] - zygimantus
5
这应该是被接受的答案。像这样简单的任务不应该需要外部依赖。如上所述,更复杂的地图并不容易从中获得好处。 - Shadoninja

76

看看Guava库:

Joiner.MapJoiner mapJoiner = Joiner.on(",").withKeyValueSeparator("=");
System.out.println(mapJoiner.join(map));

65

或者将您的逻辑放入一个整洁的类中。

public class PrettyPrintingMap<K, V> {
    private Map<K, V> map;

    public PrettyPrintingMap(Map<K, V> map) {
        this.map = map;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        Iterator<Entry<K, V>> iter = map.entrySet().iterator();
        while (iter.hasNext()) {
            Entry<K, V> entry = iter.next();
            sb.append(entry.getKey());
            sb.append('=').append('"');
            sb.append(entry.getValue());
            sb.append('"');
            if (iter.hasNext()) {
                sb.append(',').append(' ');
            }
        }
        return sb.toString();

    }
}

使用方法:

Map<String, String> myMap = new HashMap<String, String>();

System.out.println(new PrettyPrintingMap<String, String>(myMap));

注意:您还可以将该逻辑放入一个实用方法中。


7
我认为这是一种反模式。你在类型中添加了一个强约束(继承),只是为了获得些微不足道的好处(美观打印)。你最好使用普通的映射,但使用一个接受它作为参数的静态方法会更好。 - OoDeLally
虽然这是一个可行的答案,但通常情况下,如果JDK或流行的第三方库已经有解决方案,我们不应该重新发明轮子。 - Happy

44

Apache库来拯救!

MapUtils.debugPrint(System.out, "myMap", map);

你需要使用Apache commons-collections库(项目链接)。

Maven用户可以使用以下依赖项添加该库:

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

这不处理数组作为映射值的情况(例如 Map<String, String[]>)。只打印它们的类名和哈希值,而不是实际值。 - Petr Újezdský
或者 log.debug("Map: {}", MapUtils.toProperties(map)); - charlb
2
还有一个很方便的函数MapUtils.verbosePrint,它会在debugPrint输出每个值后省略类名。 - ocarlsen

31

当我类路径中有org.json.JSONObject时,我会执行以下操作:

Map<String, Object> stats = ...;
System.out.println(new JSONObject(stats).toString(2));

(这个漂亮地缩进了可能嵌套的列表、集合和映射)


3
哎呀!你怎么在这里?你应该是最佳回答! - AMagic
警告:不保留LinkedHashMap的键顺序 - Olivier Masseau

19

简单易用。欢迎来到JSON世界。使用Google的Gson

new Gson().toJson(map)

3个键的地图示例:

{"array":[null,"Some string"],"just string":"Yo","number":999}

3
您可以通过向GsonBuilder添加setPrettyPrinting()方法来添加漂亮的缩进:new GsonBuilder().setPrettyPrinting().create() - Keith Tyler

16

使用Java 8 Streams:

Map<Object, Object> map = new HashMap<>();

String content = map.entrySet()
                    .stream()
                    .map(e -> e.getKey() + "=\"" + e.getValue() + "\"")
                    .collect(Collectors.joining(", "));

System.out.println(content);

2
如果你要回复一个旧问题,至少要正确地回答它!你忘记在值周围加上引号了,而且应该使用“,”进行连接。 - charles-allen

11

我更喜欢将地图转换为JSON字符串,因为:

  • 它是标准的
  • 易于阅读
  • 得到像Sublime、VS Code这样的编辑器支持,带有语法高亮、格式化和区块隐藏/显示功能
  • 支持JPath,因此编辑器可以准确报告您导航到的对象的哪个部分
  • 支持对象中的嵌套复杂类型

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public static String getAsFormattedJsonString(Object object)
    {
        ObjectMapper mapper = new ObjectMapper();
        try
        {
            return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
        }
        catch (JsonProcessingException e)
        {
            e.printStackTrace();
        }
        return "";
    }
    

4

请查看OpenJDK源代码中HashMap#toString()AbstractMap#toString()的代码:

class java.util.HashMap.Entry<K,V> implements Map.Entry<K,V> {
       public final String toString() {
           return getKey() + "=" + getValue();
       }
   }
 class java.util.AbstractMap<K,V> {
     public String toString() {
         Iterator<Entry<K,V>> i = entrySet().iterator();
         if (! i.hasNext())
            return "{}";

        StringBuilder sb = new StringBuilder();
        sb.append('{');
        for (;;) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            sb.append(key   == this ? "(this Map)" : key);
            sb.append('=');
            sb.append(value == this ? "(this Map)" : value);
            if (! i.hasNext())
                return sb.append('}').toString();
            sb.append(", ");
        }
    }
}

如果OpenJDK的人没有找到更优雅的方法来实现这个,那就没有了 :-)

3
您可以通过执行以下操作来实现您想要的功能:System.out.println(map)。例如,只要映射中的所有对象都覆盖了toString方法,您就会以有意义的方式看到:{key1=value1, key2=value2}
如果这是针对您的代码的话,那么覆盖toString是一个好习惯,我建议您采用这种方法。对于您的示例,其中对象为String,您不需要其他任何内容。也就是说,System.out.println(map)将打印出您所需的内容,无需任何额外的代码。

1
他的键和值都是字符串;我认为他已经涵盖了toString方法。 - Tom
你说得对,但也许他只是复制了一个简单的例子来阐述他的观点。不过我会更新答案。 - Cratylus
是的,我应该指出我的映射是Map<String,String>。 但我还表明我希望在条目值中拥有更多自由,例如具有逗号分隔的列表。因此,Map.toString()无法胜任。 - space_monkey
接口java.util.Map对于toString()没有任何约定,所以实际的Map实现决定了System.out.println(map) -> PrintStream.println(map) -> String.valueOf(map) -> map.toString()会产生什么结果。常用的java.util.AbstractMap提供了一个很好的字符串表示形式来进行toString()。...其他的Map实现可能会回退到Object.toString(),这将导致不太具有信息性的com.example.MyMap@4e8c0de - Abdull

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接