简体   繁体   English

ReplaceAll与java8 lambda函数

[英]ReplaceAll with java8 lambda functions

Given the following variables 给出以下变量

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

I would like to replace the placeholder ${name} with the value "Joe" using the following code (that does not work) 我想使用以下代码(不起作用)将值占位符$ {name}替换为值“Joe”

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

However, if I do the "old-style" way, everything works perfectly: 但是,如果我采用“旧式”的方式,一切都很完美:

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

Surely I am missing something here :) 当然我在这里遗漏了一些东西:)

Java 8 Java 8

The proper way to implement this has not changed in Java 8, it is based on appendReplacement() / appendTail() : 实现这一点的正确方法在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);

Note that, as mentioned by drrob in the comments, the replacement String of appendReplacement() may contain group references using the $ sign, and escaping using \\ . 请注意,正如drrob在注释中所提到的, appendReplacement()的替换字符串可能包含使用$符号的组引用,并使用\\转义。 If this is not desired, or if your replacement String can potentially contain those characters, you should escape them using Matcher.quoteReplacement() . 如果不需要,或者替换String可能包含这些字符,则应使用Matcher.quoteReplacement()它们进行Matcher.quoteReplacement()

Being more functional in Java 8 在Java 8中更具功能性

If you want a more Java-8-style version, you can extract the search-and-replace boiler plate code into a generalized method that takes a replacement Function : 如果你想要一个更加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;
}

and use it as 并用它作为

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

Note that having a Pattern as parameter (instead of a String ) allows it to be stored as a constant instead of recompiling it every time. 请注意,将Pattern作为参数(而不是String )允许将其存储为常量,而不是每次都重新编译它。

Same remark applies as above concerning $ and \\ – you may want to enforce the quoteReplacement() inside the replaceAll() method if you don't want your replacer function to handle it. 如上所述,与$\\相同的注释 - 如果您不希望replacer函数处理它,您可能希望在replaceAll()方法中强制执行quoteReplacement()

Java 9 and above Java 9及以上版本

Java 9 introduced Matcher.replaceAll(Function) which basically implements the same thing as the functional version above. Java 9引入了Matcher.replaceAll(Function) ,它基本上实现了与上面的功能版本相同的功能。 See Jesse Glick's answer for more details. 有关详细信息,请参阅Jesse Glick的答案

you also can using Stream.reduce(identity,accumulator,combiner) . 你也可以使用Stream.reduce(identity,accumulator,combiner)

identity 身分

identity is the initial value for reducing function which is accumulator . identity是减少accumulator功能的初始值。

accumulator 累加器

accumulator reducing identity to result , which is the identity for the next reducing if the stream is sequentially . accumulatoridentity减少到result ,如果流是顺序的,则为下一个减少的identity

combiner 组合

this function never be called in sequentially stream. 永远不会在顺序流中调用此函数。 it calculate the next identity from identity & result in parallel stream. 它从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);
    }

}

output: 输出:

Hi, Joe White! 嗨,乔怀特! My name is Joe too :) 我叫乔也是:)

However , it's not the best idea to reinvent the wheel and the preferred way to achieve what you want would be to use apache commons lang as stated here . 然而 ,重新发明轮子并不是最好的想法,实现你想要的首选方法是使用这里所述的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);

Your code should be changed like below, 您的代码应该如下所示进行更改,

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)));

Performing replaceAll repeatedly, ie for every replaceable variable, can become quiet expensive, especially as the number of variables might grow. 重复执行replaceAll ,即对于每个可替换的变量,可能会变得非常昂贵,尤其是随着变量数量的增加。 This doesn't become more efficient when using the Stream API. 使用Stream API时,这不会变得更有效。 The regex package contains the necessary building blocks to do this more efficiently: 正则表达式包包含必要的构建块,以便更有效地执行此操作:

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();
}

If you are performing the operation with the same Map very often, you may consider keeping the result of Pattern.compile(pattern) , as it is immutable and safely shareable. 如果您经常使用相同的Map执行操作,您可以考虑保留Pattern.compile(pattern)的结果,因为它是不可变的并且可以安全地共享。

On the other hand, if you are using this operation with different maps frequently, it might be an option to use a generic pattern instead, combined with handling the possibility that the particular variable is not in the map. 另一方面,如果您经常使用不同映射的此操作,则可以选择使用通用模式,并结合处理特定变量不在映射中的可能性。 The adds the option to report occurrences of the ${…} pattern with an unknown variable: 添加选项以使用未知变量报告${…}模式的出现:

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) is the actual match, so using this as a fall-back for the replacement string establishes the original behavior of not replacing ${…} occurrences when the key is not in the map. m.group(0)是实际匹配,因此将此作为替换字符串的后备使用可确定当键不在映射中时不替换${…}次出现的原始行为。 As said, alternative behaviors, like reporting the absent key or using a different fall-back text, are possible. 如上所述,替代行为,例如报告缺席密钥或使用不同的后备文本,是可能的。

要更新@ 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