Java中的字符串替换,类似于力度模板


96

StringJava中是否有任何替换机制,我可以在其中传递带有文本的对象,并在出现字符串时替换它。
例如,文本为:

Hello ${user.name},
    Welcome to ${site.name}. 

我拥有的对象是"user""site"。我想${}用对象的等效值替换内部给定的字符串。这与我们替换速度模板中的对象相同。


1
替换在哪里?一类?一个JSP?如果您只是以下情况,则字符串具有一种格式化方法:String.format("Hello %s", username);
Droo 2010年

1
@Droo:在示例中,字符串是like Hello ${user.name},not like Hello %sHello {0}
Adeel Ansari'9

2
如果您需要看起来像速度并且闻起来像速度的东西,也许就是速度?:)
serg 2010年

@Droo:这不是一堂课。我在“字符串”变量中添加了上述文本,并希望将$ {}中所有出现的字符串替换为相应对象中的值。例如,将所有$ {user.name}替换为“ user”对象中的name属性。

@serg:是的,它是一个速度代码。我想从代码中消除速度。

Answers:


142

使用StringSubstitutorApache的共享文本。

https://commons.apache.org/proper/commons-text/

它将为您(及其开源...)

 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}.";
 StringSubstitutor sub = new StringSubstitutor(valuesMap);
 String resolvedString = sub.replace(templateString);


1
应该是Map<String, String> valuesMap = new HashMap<String, String>();
andrewrjones


7
StrSubstitutor从1.3版开始弃用,请StringSubstitutor改用。此类将在2.0中删除。导入的Gradle依赖项StringSubstitutororg.apache.commons:commons-text:1.4
Yurii Rabeshko

是否允许基于条件的替换?
gaurav '19

129

看一看java.text.MessageFormat该类,MessageFormat接收一组对象,对它们进行格式化,然后将格式化后的字符串插入模式中的适当位置。

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);

10
谢谢!我知道java应该有一种内置的方法来执行此操作,而不必使用怪异的模板引擎来做这样简单的事情!
Joseph Rajeev Motha 2014年

2
看起来的String.format可以做任何事情,这可怎么办- stackoverflow.com/questions/2809633/...
本体

6
+1。请注意,format还需要使用Object...可变参数,因此您可以在需要的地方使用这种更简洁的语法format("{0} world {1}", "Hello", "!");
davnicwil

应当注意,MessageFormat只能可靠地用于其名称,显示消息,而不能用于技术格式化很重要的输出。例如,数字将根据区域设置进行格式化,从而使它们对于技术用途无效。
Marnes

22

我的首选方式是String.format()因为它是一个单行,并且不需要第三方库:

String message = String.format("Hello! My name is %s, I'm %s.", name, age); 

我经常使用它,例如在异常消息中:

throw new Exception(String.format("Unable to login with email: %s", email));

提示:您可以输入任意多个变量,因为format()使用了Varargs


当您需要多次重复相同的参数时,此功能将不太有用。例如:String.format("Hello! My name is %s, I'm %s. Why is my name %s you ask? Well I'm only %s years old so I don't know", name, age, name, age);。此处的其他答案仅需要指定每个参数一次。
asherbar

2
@asherbar,您可以在格式字符串中使用参数索引说明符,例如String.format("Hello! My name is %1$s, I'm %2$s. Why is my name %1$s you ask? Well I'm only %2$s years old so I don't know", name, age)
jazzpi,

@jazzpi我从来不知道。谢谢!
asherbar

20

我整理了一个小的测试实现。基本思想是调用format并传入格式字符串,对象映射以及它们在本地具有的名称。

以下输出:

我的狗叫菲多(Fido),简·多伊(Jane Doe)拥有他。

public class StringFormatter {

    private static final String fieldStart = "\\$\\{";
    private static final String fieldEnd = "\\}";

    private static final String regex = fieldStart + "([^}]+)" + fieldEnd;
    private static final Pattern pattern = Pattern.compile(regex);

    public static String format(String format, Map<String, Object> objects) {
        Matcher m = pattern.matcher(format);
        String result = format;
        while (m.find()) {
            String[] found = m.group(1).split("\\.");
            Object o = objects.get(found[0]);
            Field f = o.getClass().getField(found[1]);
            String newVal = f.get(o).toString();
            result = result.replaceFirst(regex, newVal);
        }
        return result;
    }

    static class Dog {
        public String name;
        public String owner;
        public String gender;
    }

    public static void main(String[] args) {
        Dog d = new Dog();
        d.name = "fido";
        d.owner = "Jane Doe";
        d.gender = "him";
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("d", d);
        System.out.println(
           StringFormatter.format(
                "My dog is named ${d.name}, and ${d.owner} owns ${d.gender}.", 
                map));
    }
}

注意:由于未处理的异常,该文件无法编译。但这使代码更易于阅读。

另外,我不喜欢您必须在代码中自己构造映射,但是我不知道如何以编程方式获取局部变量的名称。最好的方法是记住在创建对象后立即将其放入地图中。

下面的示例从示例中生成所需的结果:

public static void main(String[] args) {
    Map<String, Object> map = new HashMap<String, Object>();
    Site site = new Site();
    map.put("site", site);
    site.name = "StackOverflow.com";
    User user = new User();
    map.put("user", user);
    user.name = "jjnguy";
    System.out.println(
         format("Hello ${user.name},\n\tWelcome to ${site.name}. ", map));
}

我还应该提到我不知道什么是Velocity,所以我希望这个答案是有意义的。


这就是我想要的。谢谢您的实施。我正在尝试并得到不正确的结果。:D 无论如何,它解决了我的问题。

2
@乔,很高兴我能帮上忙。对我来说,最终练习编写一些在Java中使用反射的代码是一个很好的借口。
jjnguy 2010年

6

这是您如何进行此操作的概述。将其实现为实际代码应该相对简单。

  1. 创建将在模板中引用的所有对象的映射。
  2. 使用正则表达式在模板中查找变量引用,并将其替换为其值(请参阅步骤3)。该匹配器类会派上用场的查找和替换。
  3. 在点处分割变量名称。user.name将成为usernameuser在地图中查找以获取对象,并使用反射name从对象中获取的值。假设您的对象具有标准的吸气剂,您将寻找一个方法getName并调用它。

嘿,刚看到这个答案。它和我的一样。请让我知道您对我的实施情况有何看法。
jjnguy 2010年



0

我在Java中使用GroovyShell使用Groovy GString解析模板:

Binding binding = new Binding();
GroovyShell gs = new GroovyShell(binding);
// this JSONObject can also be replaced by any Java Object
JSONObject obj = new JSONObject();
obj.put("key", "value");
binding.setProperty("obj", obj)
String str = "${obj.key}";
String exp = String.format("\"%s\".toString()", str);
String res = (String) gs.evaluate(exp);
// value
System.out.println(str);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.