在Python中,格式化字符串时,我可以按名称而不是按位置填充占位符,如下所示:

print "There's an incorrect value '%(value)s' in column # %(column)d" % \
  { 'value': x, 'column': y }

我想知道这在Java中是否可能(希望没有外部库)?


当前回答

你可以在字符串助手类上有这样的东西

/**
 * An interpreter for strings with named placeholders.
 *
 * For example given the string "hello %(myName)" and the map <code>
 *      <p>Map<String, Object> map = new HashMap<String, Object>();</p>
 *      <p>map.put("myName", "world");</p>
 * </code>
 *
 * the call {@code format("hello %(myName)", map)} returns "hello world"
 *
 * It replaces every occurrence of a named placeholder with its given value
 * in the map. If there is a named place holder which is not found in the
 * map then the string will retain that placeholder. Likewise, if there is
 * an entry in the map that does not have its respective placeholder, it is
 * ignored.
 *
 * @param str
 *            string to format
 * @param values
 *            to replace
 * @return formatted string
 */
public static String format(String str, Map<String, Object> values) {

    StringBuilder builder = new StringBuilder(str);

    for (Entry<String, Object> entry : values.entrySet()) {

        int start;
        String pattern = "%(" + entry.getKey() + ")";
        String value = entry.getValue().toString();

        // Replace every occurence of %(key) with value
        while ((start = builder.indexOf(pattern)) != -1) {
            builder.replace(start, start + pattern.length(), value);
        }
    }

    return builder.toString();
}

其他回答

谢谢你的帮助!使用所有的线索,我写了一个例程来做我想要的——使用字典的类似python的字符串格式化。因为我是Java新手,任何提示都是感激的。

public static String dictFormat(String format, Hashtable<String, Object> values) {
    StringBuilder convFormat = new StringBuilder(format);
    Enumeration<String> keys = values.keys();
    ArrayList valueList = new ArrayList();
    int currentPos = 1;
    while (keys.hasMoreElements()) {
        String key = keys.nextElement(),
        formatKey = "%(" + key + ")",
        formatPos = "%" + Integer.toString(currentPos) + "$";
        int index = -1;
        while ((index = convFormat.indexOf(formatKey, index)) != -1) {
            convFormat.replace(index, index + formatKey.length(), formatPos);
            index += formatPos.length();
        }
        valueList.add(values.get(key));
        ++currentPos;
    }
    return String.format(convFormat.toString(), valueList.toArray());
}

我最终得到了下一个解决方案: 使用substitute()方法创建类templatessubstitute,并使用它格式化输出 然后创建一个字符串模板,并用值填充它

import java.util.*;
public class MyClass {

    public static void main(String args[]) {
    String template = "WRR = {WRR}, SRR = {SRR}\n" +
                      "char_F1 = {char_F1}, word_F1 = {word_F1}\n";
    
    Map<String, Object> values = new HashMap<>();
    values.put("WRR", 99.9);
    values.put("SRR", 99.8);
    values.put("char_F1", 80);
    values.put("word_F1", 70);
    
    String message = TemplateSubstitutor.substitute(values, template);
    
    System.out.println(message);
    }
}

class TemplateSubstitutor {
    public static String substitute(Map<String, Object> map, String input_str) {
        String output_str = input_str;
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            output_str = output_str.replace("{" + key + "}", String.valueOf(value));
        }
        return output_str;
    }
    
}

Apache Commons Lang的replaceEach方法可能会根据您的特定需求派上用场。你可以简单地用这个方法调用来替换占位符:

StringUtils.replaceEach("There's an incorrect value '%(value)' in column # %(column)",
            new String[] { "%(value)", "%(column)" }, new String[] { x, y });

给定一些输入文本,这将用第二个字符串数组中的相应值替换第一个字符串数组中出现的所有占位符。

你可以使用StringTemplate库,它提供了你想要的和更多。

import org.antlr.stringtemplate.*;

final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());

I created also a util/helper class (using jdk 8) which can format a string an replaces occurrences of variables. For this purpose I used the Matchers "appendReplacement" method which does all the substitution and loops only over the affected parts of a format string. The helper class isn't currently well javadoc documented. I will changes this in the future ;) Anyway I commented the most important lines (I hope). public class FormatHelper { //Prefix and suffix for the enclosing variable name in the format string. //Replace the default values with any you need. public static final String DEFAULT_PREFIX = "${"; public static final String DEFAULT_SUFFIX = "}"; //Define dynamic function what happens if a key is not found. //Replace the defualt exception with any "unchecked" exception type you need or any other behavior. public static final BiFunction<String, String, String> DEFAULT_NO_KEY_FUNCTION = (fullMatch, variableName) -> { throw new RuntimeException(String.format("Key: %s for variable %s not found.", variableName, fullMatch)); }; private final Pattern variablePattern; private final Map<String, String> values; private final BiFunction<String, String, String> noKeyFunction; private final String prefix; private final String suffix; public FormatHelper(Map<String, String> values) { this(DEFAULT_NO_KEY_FUNCTION, values); } public FormatHelper( BiFunction<String, String, String> noKeyFunction, Map<String, String> values) { this(DEFAULT_PREFIX, DEFAULT_SUFFIX, noKeyFunction, values); } public FormatHelper(String prefix, String suffix, Map<String, String> values) { this(prefix, suffix, DEFAULT_NO_KEY_FUNCTION, values); } public FormatHelper( String prefix, String suffix, BiFunction<String, String, String> noKeyFunction, Map<String, String> values) { this.prefix = prefix; this.suffix = suffix; this.values = values; this.noKeyFunction = noKeyFunction; //Create the Pattern and quote the prefix and suffix so that the regex don't interpret special chars. //The variable name is a "\w+" in an extra capture group. variablePattern = Pattern.compile(Pattern.quote(prefix) + "(\\w+)" + Pattern.quote(suffix)); } public static String format(CharSequence format, Map<String, String> values) { return new FormatHelper(values).format(format); } public static String format( CharSequence format, BiFunction<String, String, String> noKeyFunction, Map<String, String> values) { return new FormatHelper(noKeyFunction, values).format(format); } public static String format( String prefix, String suffix, CharSequence format, Map<String, String> values) { return new FormatHelper(prefix, suffix, values).format(format); } public static String format( String prefix, String suffix, BiFunction<String, String, String> noKeyFunction, CharSequence format, Map<String, String> values) { return new FormatHelper(prefix, suffix, noKeyFunction, values).format(format); } public String format(CharSequence format) { //Create matcher based on the init pattern for variable names. Matcher matcher = variablePattern.matcher(format); //This buffer will hold all parts of the formatted finished string. StringBuffer formatBuffer = new StringBuffer(); //loop while the matcher finds another variable (prefix -> name <- suffix) match while (matcher.find()) { //The root capture group with the full match e.g ${variableName} String fullMatch = matcher.group(); //The capture group for the variable name resulting from "(\w+)" e.g. variableName String variableName = matcher.group(1); //Get the value in our Map so the Key is the used variable name in our "format" string. The associated value will replace the variable. //If key is missing (absent) call the noKeyFunction with parameters "fullMatch" and "variableName" else return the value. String value = values.computeIfAbsent(variableName, key -> noKeyFunction.apply(fullMatch, key)); //Escape the Map value because the "appendReplacement" method interprets the $ and \ as special chars. String escapedValue = Matcher.quoteReplacement(value); //The "appendReplacement" method replaces the current "full" match (e.g. ${variableName}) with the value from the "values" Map. //The replaced part of the "format" string is appended to the StringBuffer "formatBuffer". matcher.appendReplacement(formatBuffer, escapedValue); } //The "appendTail" method appends the last part of the "format" String which has no regex match. //That means if e.g. our "format" string has no matches the whole untouched "format" string is appended to the StringBuffer "formatBuffer". //Further more the method return the buffer. return matcher.appendTail(formatBuffer) .toString(); } public String getPrefix() { return prefix; } public String getSuffix() { return suffix; } public Map<String, String> getValues() { return values; } } You can create a class instance for a specific Map with values (or suffix prefix or noKeyFunction) like: Map<String, String> values = new HashMap<>(); values.put("firstName", "Peter"); values.put("lastName", "Parker"); FormatHelper formatHelper = new FormatHelper(values); formatHelper.format("${firstName} ${lastName} is Spiderman!"); // Result: "Peter Parker is Spiderman!" // Next format: formatHelper.format("Does ${firstName} ${lastName} works as photographer?"); //Result: "Does Peter Parker works as photographer?" Further more you can define what happens if a key in the values Map is missing (works in both ways e.g. wrong variable name in format string or missing key in Map). The default behavior is an thrown unchecked exception (unchecked because I use the default jdk8 Function which cant handle checked exceptions) like: Map<String, String> map = new HashMap<>(); map.put("firstName", "Peter"); map.put("lastName", "Parker"); FormatHelper formatHelper = new FormatHelper(map); formatHelper.format("${missingName} ${lastName} is Spiderman!"); //Result: RuntimeException: Key: missingName for variable ${missingName} not found. You can define a custom behavior in the constructor call like: Map<String, String> values = new HashMap<>(); values.put("firstName", "Peter"); values.put("lastName", "Parker"); FormatHelper formatHelper = new FormatHelper(fullMatch, variableName) -> variableName.equals("missingName") ? "John": "SOMETHING_WRONG", values); formatHelper.format("${missingName} ${lastName} is Spiderman!"); // Result: "John Parker is Spiderman!" or delegate it back to the default no key behavior: ... FormatHelper formatHelper = new FormatHelper((fullMatch, variableName) -> variableName.equals("missingName") ? "John" : FormatHelper.DEFAULT_NO_KEY_FUNCTION.apply(fullMatch, variableName), map); ... For better handling there are also static method representations like: Map<String, String> values = new HashMap<>(); values.put("firstName", "Peter"); values.put("lastName", "Parker"); FormatHelper.format("${firstName} ${lastName} is Spiderman!", map); // Result: "Peter Parker is Spiderman!"