Given the following variables
templateText = \"Hi ${name}\";
variables.put(\"name\", \"Joe\");
I would like to replace the placeholder ${na
To update @didier-l’s answer, in Java 9 this is a one-liner!
Pattern.compile("[$][{](.+?)[}]").matcher(templateText).replaceAll(m -> variables.get(m.group(1)))
you also can using Stream.reduce(identity,accumulator,combiner).
identity
is the initial value for reducing function which is accumulator
.
accumulator
reducing identity
to result
, which is the identity
for the next reducing if the stream is sequentially.
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);
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.
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);
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().
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 introduced Matcher.replaceAll(Function) which basically implements the same thing as the functional version above. See Jesse Glick's answer for more details.
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, i.e. for every replaceable variable, can become quiet expensive, especially as the number of variables might grow. This doesn’t become more efficient when using the 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.
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. As said, alternative behaviors, like reporting the absent key or using a different fall-back text, are possible.