繁体   English   中英

ReplaceAll与java8 lambda函数

[英]ReplaceAll with java8 lambda functions

给出以下变量

templateText = "Hi ${name}";
variables.put("name", "Joe");

我想使用以下代码(不起作用)将值占位符$ {name}替换为值“Joe”

 variables.keySet().forEach(k -> templateText.replaceAll("\\${\\{"+ k +"\\}"  variables.get(k)));

但是,如果我采用“旧式”的方式,一切都很完美:

for (Entry<String, String> entry : variables.entrySet()){
    String  regex = "\\$\\{" + entry.getKey() + "\\}";          
    templateText =  templateText.replaceAll(regex, entry.getValue());           
   }

当然我在这里遗漏了一些东西:)

Java 8

实现这一点的正确方法在Java 8中没有改变,它基于appendReplacement() / appendTail()

Pattern variablePattern = Pattern.compile("\\$\\{(.+?)\\}");
Matcher matcher = variablePattern.matcher(templateText);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
    matcher.appendReplacement(result, variables.get(matcher.group(1)));
}
matcher.appendTail(result);
System.out.println(result);

请注意,正如drrob在注释中所提到的, appendReplacement()的替换字符串可能包含使用$符号的组引用,并使用\\转义。 如果不需要,或者替换String可能包含这些字符,则应使用Matcher.quoteReplacement()它们进行Matcher.quoteReplacement()

在Java 8中更具功能性

如果你想要一个更加Java-8风格的版本,你可以将搜索和替换样板代码提取到一个带有替换Function的通用方法中:

private static StringBuffer replaceAll(String templateText, Pattern pattern,
                                       Function<Matcher, String> replacer) {
    Matcher matcher = pattern.matcher(templateText);
    StringBuffer result = new StringBuffer();
    while (matcher.find()) {
        matcher.appendReplacement(result, replacer.apply(matcher));
    }
    matcher.appendTail(result);
    return result;
}

并用它作为

Pattern variablePattern = Pattern.compile("\\$\\{(.+?)\\}");
StringBuffer result = replaceAll(templateText, variablePattern,
                                 m -> variables.get(m.group(1)));

请注意,将Pattern作为参数(而不是String )允许将其存储为常量,而不是每次都重新编译它。

如上所述,与$\\相同的注释 - 如果您不希望replacer函数处理它,您可能希望在replaceAll()方法中强制执行quoteReplacement()

Java 9及以上版本

Java 9引入了Matcher.replaceAll(Function) ,它基本上实现了与上面的功能版本相同的功能。 有关详细信息,请参阅Jesse Glick的答案

你也可以使用Stream.reduce(identity,accumulator,combiner)

身分

identity是减少accumulator功能的初始值。

累加器

accumulatoridentity减少到result ,如果流是顺序的,则为下一个减少的identity

组合

永远不会在顺序流中调用此函数。 它从identity计算下一个identityresult 并行流。

BinaryOperator<String> combinerNeverBeCalledInSequentiallyStream=(identity,t) -> {
   throw new IllegalStateException("Can't be used in parallel stream");
};

String result = variables.entrySet().stream()
            .reduce(templateText
                   , (it, var) -> it.replaceAll(format("\\$\\{%s\\}", var.getKey())
                                               , var.getValue())
                   , combinerNeverBeCalledInSequentiallyStream);
import java.util.HashMap;
import java.util.Map;

public class Repl {

    public static void main(String[] args) {
        Map<String, String> variables = new HashMap<>();
        String templateText = "Hi, ${name} ${secondname}! My name is ${name} too :)";
        variables.put("name", "Joe");
        variables.put("secondname", "White");

        templateText = variables.keySet().stream().reduce(templateText, (acc, e) -> acc.replaceAll("\\$\\{" + e + "\\}", variables.get(e)));
        System.out.println(templateText);
    }

}

输出:

嗨,乔怀特! 我叫乔也是:)

然而 ,重新发明轮子并不是最好的想法,实现你想要的首选方法是使用这里所述的apache commons lang

 Map<String, String> valuesMap = new HashMap<String, String>();
 valuesMap.put("animal", "quick brown fox");
 valuesMap.put("target", "lazy dog");
 String templateString = "The ${animal} jumped over the ${target}.";
 StrSubstitutor sub = new StrSubstitutor(valuesMap);
 String resolvedString = sub.replace(templateString);

您的代码应该如下所示进行更改,

String templateText = "Hi ${name}";
Map<String,String> variables = new HashMap<>();
variables.put("name", "Joe");
templateText = variables.keySet().stream().reduce(templateText, (originalText, key) -> originalText.replaceAll("\\$\\{" + key + "\\}", variables.get(key)));

重复执行replaceAll ,即对于每个可替换的变量,可能会变得非常昂贵,尤其是随着变量数量的增加。 使用Stream API时,这不会变得更有效。 正则表达式包包含必要的构建块,以便更有效地执行此操作:

public static String replaceAll(String template, Map<String,String> variables) {
    String pattern = variables.keySet().stream()
        .map(Pattern::quote)
        .collect(Collectors.joining("|", "\\$\\{(", ")\\}"));
    Matcher m = Pattern.compile(pattern).matcher(template);
    if(!m.find()) {
        return template;
    }
    StringBuffer sb = new StringBuffer();
    do {
        m.appendReplacement(sb, Matcher.quoteReplacement(variables.get(m.group(1))));
    } while(m.find());
    m.appendTail(sb);
    return sb.toString();
}

如果您经常使用相同的Map执行操作,您可以考虑保留Pattern.compile(pattern)的结果,因为它是不可变的并且可以安全地共享。

另一方面,如果您经常使用不同映射的此操作,则可以选择使用通用模式,并结合处理特定变量不在映射中的可能性。 添加选项以使用未知变量报告${…}模式的出现:

private static Pattern VARIABLE = Pattern.compile("\\$\\{([^}]*)\\}");
public static String replaceAll(String template, Map<String,String> variables) {
    Matcher m = VARIABLE.matcher(template);
    if(!m.find())
        return template;
    StringBuffer sb = new StringBuffer();
    do {
        m.appendReplacement(sb,
            Matcher.quoteReplacement(variables.getOrDefault(m.group(1), m.group(0))));
    } while(m.find());
    m.appendTail(sb);
    return sb.toString();
}

m.group(0)是实际匹配,因此将此作为替换字符串的后备使用可确定当键不在映射中时不替换${…}次出现的原始行为。 如上所述,替代行为,例如报告缺席密钥或使用不同的后备文本,是可能的。

要更新@ didier-l的答案,在Java 9中这是一个单行程!

Pattern.compile("[$][{](.+?)[}]").matcher(templateText).replaceAll(m -> variables.get(m.group(1)))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM