Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

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

like image 963
user3727540 Avatar asked Apr 12 '17 13:04

user3727540


2 Answers

Java 8

The proper way to implement this has not changed in Java 8, it is based on 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 \. If this is not desired, or if your replacement String can potentially contain those characters, you should escape them using Matcher.quoteReplacement().

Being more functional in 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:

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.

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.

Java 9 and above

Java 9 introduced Matcher.replaceAll(Function) which basically implements the same thing as the functional version above. See Jesse Glick's answer for more details.

like image 127
Didier L Avatar answered Nov 16 '22 03:11

Didier L


you also can using Stream.reduce(identity,accumulator,combiner).

identity

identity is the initial value for reducing function which is accumulator.

accumulator

accumulator reducing identity to result, which is the identity for the next reducing if the stream is sequentially.

combiner

this function never be called in sequentially stream. it calculate the next identity from identity & result in parallel stream.

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);
like image 10
holi-java Avatar answered Nov 16 '22 03:11

holi-java