In Python, when formatting string, I can fill placeholders by name rather than by position, like that:
print \"There\'s an incorrect value \'%(value)s\' in c
You should have a look at the official ICU4J library. It provides a MessageFormat class similar to the one available with the JDK but this former supports named placeholders.
Unlike other solutions provided on this page. ICU4j is part of the ICU project that is maintained by IBM and regularly updated. In addition, it supports advanced use cases such as pluralization and much more.
Here is a code example:
MessageFormat messageFormat =
new MessageFormat("Publication written by {author}.");
Map<String, String> args = Map.of("author", "John Doe");
System.out.println(messageFormat.format(args));
https://dzone.com/articles/java-string-format-examples String.format(inputString, [listOfParams]) would be the easiest way. Placeholders in string can be defined by order. For more details check the provided link.
There is nothing built into Java at the moment of writing this. I would suggest writing your own implementation. My preference is for a simple fluent builder interface instead of creating a map and passing it to function -- you end up with a nice contiguous chunk of code, for example:
String result = new TemplatedStringBuilder("My name is {{name}} and I from {{town}}")
.replace("name", "John Doe")
.replace("town", "Sydney")
.finish();
Here is a simple implementation:
class TemplatedStringBuilder {
private final static String TEMPLATE_START_TOKEN = "{{";
private final static String TEMPLATE_CLOSE_TOKEN = "}}";
private final String template;
private final Map<String, String> parameters = new HashMap<>();
public TemplatedStringBuilder(String template) {
if (template == null) throw new NullPointerException();
this.template = template;
}
public TemplatedStringBuilder replace(String key, String value){
parameters.put(key, value);
return this;
}
public String finish(){
StringBuilder result = new StringBuilder();
int startIndex = 0;
while (startIndex < template.length()){
int openIndex = template.indexOf(TEMPLATE_START_TOKEN, startIndex);
if (openIndex < 0){
result.append(template.substring(startIndex));
break;
}
int closeIndex = template.indexOf(TEMPLATE_CLOSE_TOKEN, openIndex);
if(closeIndex < 0){
result.append(template.substring(startIndex));
break;
}
String key = template.substring(openIndex + TEMPLATE_START_TOKEN.length(), closeIndex);
if (!parameters.containsKey(key)) throw new RuntimeException("missing value for key: " + key);
result.append(template.substring(startIndex, openIndex));
result.append(parameters.get(key));
startIndex = closeIndex + TEMPLATE_CLOSE_TOKEN.length();
}
return result.toString();
}
}
Based on the answer I created MapBuilder
class:
public class MapBuilder {
public static Map<String, Object> build(Object... data) {
Map<String, Object> result = new LinkedHashMap<>();
if (data.length % 2 != 0) {
throw new IllegalArgumentException("Odd number of arguments");
}
String key = null;
Integer step = -1;
for (Object value : data) {
step++;
switch (step % 2) {
case 0:
if (value == null) {
throw new IllegalArgumentException("Null key value");
}
key = (String) value;
continue;
case 1:
result.put(key, value);
break;
}
}
return result;
}
}
then I created class StringFormat
for String formatting:
public final class StringFormat {
public static String format(String format, Object... args) {
Map<String, Object> values = MapBuilder.build(args);
for (Map.Entry<String, Object> entry : values.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
format = format.replace("$" + key, value.toString());
}
return format;
}
}
which you could use like that:
String bookingDate = StringFormat.format("From $startDate to $endDate"),
"$startDate", formattedStartDate,
"$endDate", formattedEndDate
);
My answer is to:
a) use StringBuilder when possible
b) keep (in any form: integer is the best, speciall char like dollar macro etc) position of "placeholder" and then use StringBuilder.insert()
(few versions of arguments).
Using external libraries seems overkill and I belive degrade performance significant, when StringBuilder is converted to String internally.
This is an old thread, but just for the record, you could also use Java 8 style, like this:
public static String replaceParams(Map<String, String> hashMap, String template) {
return hashMap.entrySet().stream().reduce(template, (s, e) -> s.replace("%(" + e.getKey() + ")", e.getValue()),
(s, s2) -> s);
}
Usage:
public static void main(String[] args) {
final HashMap<String, String> hashMap = new HashMap<String, String>() {
{
put("foo", "foo1");
put("bar", "bar1");
put("car", "BMW");
put("truck", "MAN");
}
};
String res = replaceParams(hashMap, "This is '%(foo)' and '%(foo)', but also '%(bar)' '%(bar)' indeed.");
System.out.println(res);
System.out.println(replaceParams(hashMap, "This is '%(car)' and '%(foo)', but also '%(bar)' '%(bar)' indeed."));
System.out.println(replaceParams(hashMap, "This is '%(car)' and '%(truck)', but also '%(foo)' '%(bar)' + '%(truck)' indeed."));
}
The output will be:
This is 'foo1' and 'foo1', but also 'bar1' 'bar1' indeed.
This is 'BMW' and 'foo1', but also 'bar1' 'bar1' indeed.
This is 'BMW' and 'MAN', but also 'foo1' 'bar1' + 'MAN' indeed.