如何使用流将所有映射 key-values 连接成一个字符串,并对每个值进行替换?

How to concatenate all map key-values into a string, using streams, with replacements made on each value?

我是一个 Java 7 岁的人,并且被命令永远不要再使用 for-loop,只有部分舌头在脸颊上。我需要一个 Map<String, String>,并以一串 command-line 参数结束,如以下代码的输出(暂时忽略 "streamy" 行):

forLoop: --name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name1 "value1"
resetAllValues: --name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name1 "value1"
streamy: --name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name1 "value1"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name1 "value1"

names/keys保持不变。这些值需要放在双引号中,并且需要转义所有现有的双引号 (\")。 close-quotes 应该 后跟 space.

我在 forLoop 中以 pre-java-8 的方式完成了它,在 resetAllValues 中部分使用了流,这实际上改变了每个条目。 streamy 一个是错误的,因为它重复将 all 当前输出附加到下一个元素...

如何使用流有效地完成这项工作?以及如何以不改变地图条目或使用构建器的方式完成它?我还没看到。

package working;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class Temp {
   public static void main(String[] cmd_lineParams) {
      Map<String, String> map = new HashMap<>(5);
      map.put("name1", "value1");
      map.put("name2", "{\"x\": \"y\"}");
      map.put("name3", "value3");
      map.put("name4", "value4");
      map.put("name5", "value5");
      forLoop(map);
      resetAllValues(new HashMap<String, String>(map));
      streamy(new HashMap<String, String>(map));
   }
   private static final Matcher matcher = Pattern.compile("\"").matcher("ignored input");
   private static final void forLoop(Map<String, String> map) {
      StringBuilder builder = new StringBuilder();
      for(Map.Entry<String, String> entry : map.entrySet()) {
         String value = matcher.reset(entry.getValue()).replaceAll("\\\"");
         builder.append("--").append(entry.getKey()).append(" \"").append(value).append("\"");
      }
      System.out.println("forLoop: " + builder.toString());
   }

继续...

   private static final void resetAllValues(Map<String, String> map) {
      map = map.entrySet().stream()
         .collect(Collectors.toMap(entry -> entry.getKey(),
                                   entry -> matcher.reset(entry.getValue()).replaceAll("\\\\"")));
      StringBuilder builder = new StringBuilder();
      for(Map.Entry<String, String> entry : map.entrySet()) {
         builder.append("--").append(entry.getKey()).append(" \"").append(entry.getValue()).append("\"");
      }
      System.out.println("resetAllValues: " + builder.toString());
   }
   private static final void streamy(Map<String, String> map) {
      StringBuilder builder = new StringBuilder();
      map.forEach((k,v) -> builder.append(
         builder.append("--").append(k).append(" \"").append(
            matcher.reset(v).replaceAll("\\\"")).append("\"")));
      System.out.println("streamy: " + builder.toString());
   }
}

(我的nine-year-old说我需要在这个问题的某处说"difficulty"。所以:困难。)

我想既然你说的难,那我就得回答一下了! :)

map.entrySet().stream().map((entry) -> //stream each entry, map it to string value
            "--" + entry.getKey() + " \"" + entry.getValue().replaceAll("\"", "\\\"") + "\"")
            .collect(Collectors.joining(" ")); //and 'collect' or put them together by joining

我个人不喜欢使用流,因为它很快就会变得丑陋,但它对于更简单的循环很有用。 (例如,将所有值与某个字符连接起来)然而,有了这个,您可以使用 parallelStream() 而不是 stream()

轻松并行化它

如果您希望值按某种顺序排列,这样它就不会那么随机(就像 HashMap 那样),您可以在映射之前进行排序:

.stream().sort((e1, e2) -> e1.getValue().compareTo(e2.getValue()))
.map(...)...

记住那些是 Map.Entry 个对象。

更新: 下面 Tagir Valeev 的回答更好,因为它展示了最佳实践,而不仅仅是让它发挥作用。它还弥补了几年前我写这个答案时对流 + lambda 的最初抱怨(变得太丑了)。

始终尝试将复杂的问题分解为简单且独立的部分。这里(对于流和非流解决方案)最好将转义代码放入单独的方法中:

static String quoteValue(String value) {
    return "\"" + value.replaceAll("\"", "\\\"") + "\"";
}

这样你就有了一段语义清晰的单独代码,可以单独重用和测试。例如,将来您可能还需要转义反斜杠符号,因为目前您可能无法解码最初混合有反斜杠和引号的字符串。如果你有一个单独的 quoteValue 方法(这样你不应该创建测试映射,只是测试字符串),调试这个问题会容易得多。

之后流解决方案变得不那么混乱了:

map.entrySet().stream().map(entry ->
        "--" + entry.getKey() + " "+ quoteValue(entry.getValue()))
        .collect(joining(" "));

您还可以进一步添加一种方法来格式化整个条目:

static String formatEntry(String key, String value) {
    return "--" + key + " " + quoteValue(value);
}

map.entrySet().stream().map(e -> formatEntry(e.getKey(), e.getValue()))
                       .collect(joining(" "));