Substituição de string em java, semelhante a um modelo de velocidade

96

Existe algum Stringmecanismo de substituição em Java, onde posso passar objetos com um texto, e ele substitui a string conforme ela ocorre.
Por exemplo, o texto é:

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

Os objetos que tenho são "user"e "site". Quero substituir as strings fornecidas ${}por seus valores equivalentes dos objetos. Isso é o mesmo que substituímos objetos em um modelo de velocidade.

Joe
fonte
1
Substituir onde? Uma aula? A JSP? String tem um método de formatação se você apenas:String.format("Hello %s", username);
Droo
1
@Droo: No exemplo, string é semelhante Hello ${user.name}, não semelhante Hello %sou Hello {0}.
Adeel Ansari
2
Se você precisa de algo que se pareça com velocidade e cheire a velocidade, talvez seja velocidade? :)
serg
@Droo: Não é uma aula. Eu tenho o texto acima em uma variável "String" e deseja substituir todas as ocorrências das strings dentro de $ {} por valores nos objetos correspondentes. por exemplo, substitua tudo $ {user.name} pela propriedade name no objeto "user".
Joe,
@serg: Sim, é um código de velocidade. e eu quero remover a velocidade do meu código.
Joe,

Respostas:

142

Use StringSubstitutordo texto do Apache Commons.

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

Ele fará isso por você (e seu código aberto ...)

 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);
JH.
fonte
1
Deve ser Map<String, String> valuesMap = new HashMap<String, String>();.
andrewrjones
3
StrSubstitutoragora está obsoleto em https://commons.apache.org/proper/commons-lang/ . Usuário https://commons.apache.org/proper/commons-text/ vez
Lukuluba
7
StrSubstitutorobsoleto a partir de 1.3, use em seu StringSubstitutorlugar. Esta classe será removida em 2.0. Dependência do Gradle para importação StringSubstitutoréorg.apache.commons:commons-text:1.4
Yurii Rabeshko
permite a substituição baseada em condições?
Gaurav
130

Dê uma olhada na java.text.MessageFormatclasse, MessageFormat pega um conjunto de objetos, formata-os e, em seguida, insere as strings formatadas no padrão nos locais apropriados.

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);
RealHowTo
fonte
10
Obrigado! Eu sabia que o java deveria ter uma maneira embutida de fazer isso sem ter que usar um mecanismo de template para fazer uma coisa tão simples!
Joseph Rajeev Motha de
2
Parece que String.format pode fazer qualquer coisa que isso possa fazer - stackoverflow.com/questions/2809633/…
Noumenon
6
+1. Esteja ciente formattambém leva um Object...varargs para que você possa usar esta sintaxe mais concisa onde preferirformat("{0} world {1}", "Hello", "!");
davnicwil
Deve-se notar que MessageFormatsó pode ser usado de forma confiável para mensagens de exibição de seu homônimo, não para saída onde a formatação técnica é importante. Os números, por exemplo, serão formatados de acordo com as configurações locais, tornando-os inválidos para usos técnicos.
Marnes
22

Minha maneira preferida é String.format()porque é oneliner e não requer bibliotecas de terceiros:

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

Eu uso isso regularmente, por exemplo, em mensagens de exceção como:

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

Dica: você pode inserir quantas variáveis ​​quiser porque format()usa Varargs

Artgrohe
fonte
Isso é menos útil quando você precisa repetir o mesmo argumento mais de uma vez. Por exemplo: 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);. Outras respostas aqui requerem a especificação de cada argumento apenas uma vez.
Asherbar
2
@asherbar você pode usar especificadores de índice de argumento na string de formato, por exemploString.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 Eu nunca soube disso. Obrigado!
asherbar
20

Eu fiz um pequeno teste de implementação disso. A ideia básica é chamarformat e passar a string de formato, um mapa de objetos e os nomes que eles possuem localmente.

O resultado do seguinte é:

Meu cachorro se chama fido, e Jane Doe é o dono dele.

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));
    }
}

Observação: isso não é compilado devido a exceções não tratadas. Mas torna o código muito mais fácil de ler.

Além disso, não gosto que você tenha que construir o mapa sozinho no código, mas não sei como obter os nomes das variáveis ​​locais programaticamente. A melhor maneira de fazer isso é lembrar de colocar o objeto no mapa assim que criá-lo.

O exemplo a seguir produz os resultados que você deseja do seu exemplo:

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));
}

Devo também mencionar que não tenho ideia do que é velocidade, então espero que esta resposta seja relevante.

jjnguy
fonte
Isso é o que eu estava procurando. Obrigado por dar uma implementação. Eu estava tentando e obtendo resultados incorretos. : D. De qualquer forma, resolveu meu problema.
Joe,
2
@Joe, que bom que pude ajudar. Foi uma boa desculpa para finalmente praticar a escrita de algum código que usa reflexão em Java.
jjnguy
6

Aqui está um esboço de como você poderia fazer isso. Deve ser relativamente simples implementá-lo como código real.

  1. Crie um mapa de todos os objetos que serão referenciados no modelo.
  2. Use uma expressão regular para encontrar referências de variáveis ​​no modelo e substitua-as por seus valores (consulte a etapa 3). The Matcher classe será útil para localizar e substituir.
  3. Divida o nome da variável no ponto. user.namese tornaria usere name. Procure userem seu mapa para obter o objeto e use a reflexão para obter o valor de namedo objeto. Supondo que seus objetos tenham getters padrão, você procurará um método getNamee o chamará .
Casablanca
fonte
Heh, acabei de ver esta resposta. É idêntico ao meu. Deixe-me saber o que você acha da minha implementação.
jjnguy
5

Existem algumas implementações de Expression Language por aí que fazem isso para você, pode ser preferível usar sua própria implementação conforme ou se seus requisitos aumentarem, consulte por exemplo JUEL e MVEL

Eu gosto e tenho usado o MVEL com sucesso em pelo menos um projeto.

Veja também a postagem JSTL / JSP EL (linguagem de expressão) do Stackflow em um contexto não JSP (autônomo)

Christoffer Soop
fonte
0

Não há nada fora da caixa que seja comparável à velocidade, já que a velocidade foi escrita para resolver exatamente esse problema. A coisa mais próxima que você pode tentar é olhar para o Formatador

http://cupi2.uniandes.edu.co/site/images/recursos/javadoc/j2se/1.5.0/docs/api/java/util/Formatter.html

No entanto, o formatador, até onde eu sei, foi criado para fornecer opções de formatação semelhantes ao C em Java, então ele pode não arranhar exatamente a sua coceira, mas você pode tentar :).

Mike Milkin
fonte
0

Eu uso GroovyShell em java para analisar o modelo com 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);
Kan
fonte