ReplaceAll with java8 lambda functions

Given the following variables

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

I would like to replace placeholder $ {name} with the value "Joe" using the following code (which doesn't work)

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

However, if I do the "old fashioned" way, everything works fine:

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

Of course, I missed something here :)

+9
source share
6 answers

You can also use Stream.reduce (personality, battery, combiner) .

identity

identity is the initial value for the abbreviation of the function, which is accumulator .

battery

accumulator decreasing identity to result , which is identity for the next decrease if the stream is sequential .

combiner

this function is never called in a serial stream. this computes the next identity from identity & result in a parallel thread.

 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); 
+9
source

Java 8

The correct 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 in drrob's comments, the replacement string in appendReplacement() may contain group links using the $ sign and escaping using \ . If this is undesirable or if your replacement string may contain these characters, you should escape them with Matcher.quoteReplacement() .

Be more functional in Java 8

If you want a more Java-8-style version, you can extract the search and replace the boiler plate codes in a generalized method that accepts a Function replacement:

 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 the Pattern parameter as a parameter (instead of String ) allows you to save it as a constant, rather than recompiling it every time.

The same applies to quoteReplacement() above regarding $ and \ - you may want to apply quoteReplacement() inside replaceAll() if you do not want your replacer function replacer handle it.

Java 9 and higher

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.

+16
source
 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 , this is not a good idea to reinvent the wheel, and the preferred way to achieve what you want is to use apache commons lang , as mentioned here .

  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); 
+4
source

Your code should be modified as shown 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))); 
+2
source

Repeated execution of replaceAll , i.e. for each variable variable, it can become quite expensive, especially as the number of variables increases. This does not become more efficient when using the Stream API. The regex package contains the necessary building blocks to make this more efficient:

 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 perform the operation with the same Map very often, you might consider saving the result of Pattern.compile(pattern) , since it is immutable and securely shared.

On the other hand, if you often use this operation with different cards, it is possible that a common template will be used instead, in combination with processing the possibility that a particular variable is not on the map. Adds the ability to report the appearance of the template ${…} 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 an actual match, so using it as a falloff for a replacement string sets the original behavior to not replace ${…} occurrences when the key is not on the map. As said, alternative behaviors are possible, such as reporting a missing key or using other feedback text.

+1
source

To update @ didier-ls answer, in Java 9 it is a single line interface!

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

Source: https://habr.com/ru/post/1266650/


All Articles