Espaços reservados nomeados na formatação de string

174

No Python, ao formatar a string, posso preencher espaços reservados por nome e não por posição, assim:

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

Gostaria de saber se isso é possível em Java (espero, sem bibliotecas externas)?

Andy
fonte
Você pode estender o MessageFormat e implementar a funcionalidade de mapeamento de variáveis ​​para índices.
Vpram86
1
Um pouco de história: o Java copiou principalmente o C / C ++ nesse assunto, pois tentava atrair desenvolvedores do mundo C ++, onde %sera prática comum. pt.wikipedia.org/wiki/Printf_format_string#History Observe também que alguns IDE e FindBugs podem detectar automaticamente contagens% s e% d incompatíveis, mas eu ainda preferiria os campos nomeados.
Christophe Roussy

Respostas:

143

O StrSubstitutor de jakarta commons lang é uma maneira leve de fazer isso, desde que seus valores já estejam formatados corretamente.

http://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/text/StrSubstitutor.html

Map<String, String> values = new HashMap<String, String>();
values.put("value", x);
values.put("column", y);
StrSubstitutor sub = new StrSubstitutor(values, "%(", ")");
String result = sub.replace("There's an incorrect value '%(value)' in column # %(column)");

O resultado acima resulta em:

"Existe um valor incorreto '1' na coluna # 2"

Ao usar o Maven, você pode adicionar essa dependência ao seu pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>
schup
fonte
2
Achei decepcionante que a biblioteca não atire se as chaves não forem encontradas; no entanto, se você usar a sintaxe padrão ( ${arg}) em vez da personalizada acima ( %(arg)), o regex não será compilado, o que é o efeito desejado.
John Lehmann
2
Você pode definir um VariableResolver personalizado e lançar uma exceção se a chave não estiver presente no mapa.
21416
7
Tópico antigo, mas a partir da versão 3.6, o pacote de texto foi preterido em favor do texto comum. commons.apache.org/proper/commons-text
Jeff Walker
73

não é bem assim, mas você pode usar o MessageFormat para referenciar um valor várias vezes:

MessageFormat.format("There's an incorrect value \"{0}\" in column # {1}", x, y);

O exemplo acima também pode ser feito com String.format (), mas acho a sintaxe messageFormat mais limpa, se você precisar criar expressões complexas, além de não precisar se preocupar com o tipo de objeto que está colocando na string

giladbu
fonte
Não sei por que você não pode, a posição na string não é importante, apenas a posição na lista de argumentos, o que o torna um problema de renomeação. Você sabe o nome das chaves, o que significa que você pode decidir uma posição para uma chave na lista de argumentos. a partir de agora, o valor será conhecido como 0 e a coluna 1: MessageeFormat.format ("Existe um valor incorreto \" {0} \ "na coluna # {1}, usar {0} como valor pode causar muitos problemas", valueMap .get ('valor'), valueMap.get ('coluna'));
giladbu
1
Obrigado por uma pista, ele me ajudou a escrever uma função simples que faz exatamente o que eu quero (coloquei abaixo).
19410 Andy
1
Concordado, a sintaxe é muito mais limpa. Pena que o MessageFormat tenha uma ideia própria quando se trata de formatar valores numéricos.
Kees de Kooter
E parece ignorar os espaços reservados entre aspas simples.
Kees de Kooter
MessageFormaté grande, mas pesado para relativamente grande conteúdo json
EliuX
32

Outro exemplo do Apache Common StringSubstitutor para um espaço reservado simples nomeado.

String template = "Welcome to {theWorld}. My name is {myName}.";

Map<String, String> values = new HashMap<>();
values.put("theWorld", "Stackoverflow");
values.put("myName", "Thanos");

String message = StringSubstitutor.replace(template, values, "{", "}");

System.out.println(message);

// Welcome to Stackoverflow. My name is Thanos.
Ninh Pham
fonte
Se você espera carregar arquivos muito grandes, achei que esta biblioteca também suporta replaceInquais valores substitutos em um buffer: StringBuilder ou TextStringBuilder. Com essa abordagem, todo o conteúdo do arquivo não será carregado na memória.
Edward Corrigall
15

Você pode usar a biblioteca StringTemplate , ela oferece o que você deseja e muito mais.

import org.antlr.stringtemplate.*;

final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());
Fixpoint
fonte
Problemas teve com o 'caractere:unexpected char: '''
AlikElzin-Kilaka
11

Para casos muito simples, você pode simplesmente usar uma substituição de String codificada, sem a necessidade de uma biblioteca:

    String url = "There's an incorrect value '%(value)' in column # %(column)";
    url = url.replace("%(value)", x); // 1
    url = url.replace("%(column)", y); // 2

AVISO : Eu só queria mostrar o código mais simples possível. Obviamente, NÃO use isso para código de produção sério, onde a segurança é importante, conforme declarado nos comentários: escape, manipulação de erros e segurança são um problema aqui. Mas no pior dos casos, agora você sabe por que é necessário usar uma lib 'boa' :-)

Christophe Roussy
fonte
1
este é simples e fácil, mas a desvantagem é que falha silenciosamente quando o valor não foi encontrado. Apenas deixa o espaço reservado na string original.
Kryysktos
@kiedysktos, você pode melhorá-lo fazendo uma verificação, mas se você quer a coisa completa, use um lib :)
Christophe Roussy
2
Aviso: Como essa técnica trata os resultados intermediários de substituição como cadeias de formato próprias, esta solução é vulnerável a ataques de cadeia de formatação . Qualquer solução correta deve fazer uma única passagem pela string de formato.
200_success 15/0518
@ 200_success Sim bom ponto de falar de segurança, é claro, este código não é para o uso de produção sério ...
Christophe Roussy
8

Obrigado por toda sua ajuda! Usando todas as suas pistas, escrevi uma rotina para fazer exatamente o que eu quero - formatação de string semelhante a python usando dicionário. Desde que eu sou novato em Java, todas as dicas são bem-vindas.

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());
}
Andy
fonte
Ao contrário da resposta de Lombo, isso não pode ficar preso em um loop infinito, pois formatPosnão pode conter formatKey.
Aaron Dufour
6
Aviso: Como o loop trata os resultados da substituição intermediária como cadeias de formato próprias, esta solução é vulnerável a ataques de cadeia de formatação . Qualquer solução correta deve fazer uma única passagem pela string de formato.
200_success 15/0518
6

Este é um encadeamento antigo, mas apenas para constar, você também pode usar o estilo Java 8, assim:

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

Uso:

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

A saída será:

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.
gil.fernandes
fonte
Isso é brilhante, mas infelizmente viola as especificações aqui docs.oracle.com/javase/8/docs/api/java/util/stream/… A função combinadora deve retornar o segundo parâmetro se o primeiro parâmetro for a identidade. O acima retornaria a identidade. Ele também violar esta regra: combiner.apply (u, accumulator.apply (identidade, t)) == accumulator.apply (u, t)
Ali Cheaito
Interessante ... mas apenas se você propor uma maneira melhor de passar o mapa, também se possível após o modelo, como a maioria dos códigos de formatação.
Christophe Roussy
4
Aviso: Como os .reduce()resultados da substituição intermediária são tratados como cadeias de formato próprias, esta solução é vulnerável a ataques de cadeia de formatação . Qualquer solução correta deve fazer uma única passagem pela string de formato.
200_success 15/0518
6
public static String format(String format, Map<String, Object> values) {
    StringBuilder formatter = new StringBuilder(format);
    List<Object> valueList = new ArrayList<Object>();

    Matcher matcher = Pattern.compile("\\$\\{(\\w+)}").matcher(format);

    while (matcher.find()) {
        String key = matcher.group(1);

        String formatKey = String.format("${%s}", key);
        int index = formatter.indexOf(formatKey);

        if (index != -1) {
            formatter.replace(index, index + formatKey.length(), "%s");
            valueList.add(values.get(key));
        }
    }

    return String.format(formatter.toString(), valueList.toArray());
}

Exemplo:

String format = "My name is ${1}. ${0} ${1}.";

Map<String, Object> values = new HashMap<String, Object>();
values.put("0", "James");
values.put("1", "Bond");

System.out.println(format(format, values)); // My name is Bond. James Bond.
kayz1
fonte
2
Essa deve ser a resposta, pois evita os ataques de cadeia de formato aos quais a maioria das outras soluções aqui são vulneráveis. Observe que o Java 9 torna muito mais simples, com suporte para .replaceAll()retornos de chamada de substituição de cadeia .
200_success 15/0518
Essa deve ser a resposta, pois ela não usa nenhuma biblioteca externa.
Bohao LI
3

Eu sou o autor de uma pequena biblioteca que faz exatamente o que você deseja:

Student student = new Student("Andrei", 30, "Male");

String studStr = template("#{id}\tName: #{st.getName}, Age: #{st.getAge}, Gender: #{st.getGender}")
                    .arg("id", 10)
                    .arg("st", student)
                    .format();
System.out.println(studStr);

Ou você pode encadear os argumentos:

String result = template("#{x} + #{y} = #{z}")
                    .args("x", 5, "y", 10, "z", 15)
                    .format();
System.out.println(result);

// Output: "5 + 10 = 15"
Andrei Ciobanu
fonte
é possível fazer formatação baseada em condições com sua biblioteca?
gaurav
@gaurav não é bem assim. Se você precisar, precisa de uma biblioteca de modelos com todos os recursos.
Andrei Ciobanu
2

O método replaceEach do Apache Commons Lang pode ser útil dependendo de suas necessidades específicas. Você pode usá-lo facilmente para substituir espaços reservados pelo nome com esta única chamada de método:

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

Dado algum texto de entrada, isso substituirá todas as ocorrências dos espaços reservados na primeira matriz de string pelos valores correspondentes na segunda.

Pyves
fonte
1

Você pode ter algo assim em uma classe auxiliar de string

/**
 * 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();
}
Lombo
fonte
Muito obrigado, ele faz quase o que eu quero, mas a única coisa é que não leva em conta modificadores (considerar "% (key) 08D")
Andy
1
Observe também que isso entra em um loop infinito se qualquer um dos valores usados ​​contiver a entrada correspondente.
Aaron Dufour
1
Aviso: Como o loop trata os resultados da substituição intermediária como cadeias de formato próprias, esta solução é vulnerável a ataques de cadeia de formatação . Qualquer solução correta deve fazer uma única passagem pela string de formato.
200_success 15/05/19
1

Minha resposta é:

a) use StringBuilder quando possível

b) mantenha (em qualquer forma: número inteiro é o melhor, caracteres especiais como macro de dólar etc.) posição de "espaço reservado" e use StringBuilder.insert() (poucas versões de argumentos).

O uso de bibliotecas externas parece um exagero e acredito degradar o desempenho de maneira significativa, quando StringBuilder é convertido em String internamente.

Jacek Cz
fonte
1

Com base na resposta que criei MapBuilderclasse:

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

}

então eu criei a classe StringFormatpara formatação de String:

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

}

que você pode usar assim:

String bookingDate = StringFormat.format("From $startDate to $endDate"), 
        "$startDate", formattedStartDate, 
        "$endDate", formattedEndDate
);
Rafal Enden
fonte
1
Aviso: Como o loop trata os resultados da substituição intermediária como cadeias de formato próprias, esta solução é vulnerável a ataques de cadeia de formatação . Qualquer solução correta deve fazer uma única passagem pela string de formato.
200_success 15/05/19
1

Criei também uma classe util / helper (usando o jdk 8) que pode formatar uma string e substituir ocorrências de variáveis.

Para esse propósito, usei o método "appendReplacement" do Matchers, que faz toda a substituição e faz loops apenas nas partes afetadas de uma string de formato.

Atualmente, a classe auxiliar não está bem documentada pelo javadoc. Vou mudar isso no futuro;) De qualquer forma, comentei as linhas mais importantes (espero).

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

Você pode criar uma instância de classe para um mapa específico com valores (ou prefixo de sufixo ou noKeyFunction) como:

    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?"

Além disso, você pode definir o que acontece se uma chave no mapa de valores estiver ausente (funciona nos dois sentidos, por exemplo, nome incorreto da variável na string de formato ou chave ausente no mapa). O comportamento padrão é uma exceção não verificada lançada (desmarcada porque eu uso a função jdk8 padrão que não pode lidar com exceções verificadas) como:

    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.

Você pode definir um comportamento customizado na chamada do construtor, como:

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!"

ou delegá-lo de volta ao comportamento padrão sem chave:

...
    FormatHelper formatHelper = new FormatHelper((fullMatch, variableName) ->   variableName.equals("missingName") ? "John" :
            FormatHelper.DEFAULT_NO_KEY_FUNCTION.apply(fullMatch,
                                                       variableName), map);
...

Para um melhor manuseio, também existem representações estáticas de métodos, como:

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!"
schlegel11
fonte
1

Não há nada incorporado ao Java no momento de escrever isso. Eu sugeriria escrever sua própria implementação. Minha preferência é por uma interface simples de construtor fluente, em vez de criar um mapa e passá-lo para funcionar - você acaba com um bom pedaço de código contíguo, por exemplo:

String result = new TemplatedStringBuilder("My name is {{name}} and I from {{town}}")
   .replace("name", "John Doe")
   .replace("town", "Sydney")
   .finish();

Aqui está uma implementação simples:

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();
    }
}
Jason
fonte
0

Experimente o Freemarker , biblioteca de modelos.

texto alternativo

Boris Pavlović
fonte
4
Freemarker? Eu acho que ele está disposto a saber, como fazer isso em Java simples. De qualquer forma, se o Freemarker é a resposta provável, posso dizer que o JSP também será a resposta correta?
Rakesh Juyal
1
Obrigado, mas, para a minha tarefa em mãos, isso parece um exagero. Mas obrigada.
Andy
1
@Rakesh JSP é uma coisa muito específica "view / FE". Eu usei o FreeMarker no passado para gerar XML e às vezes até gerava arquivos JAVA. Andy tenho medo que você terá que escrever um utilitário de si mesmo (ou como o prescrito acima)
Kannan Ekanath
@ Boris qual é o melhor freemarker vs velocidade vs stringtemplate?
precisa saber é
1
@gaurav dê uma olhada em stackoverflow.com/questions/3741618/… e dzone.com/articles/…
Boris Pavlović
0

Você deve dar uma olhada na biblioteca oficial do ICU4J . Ele fornece uma classe MessageFormat semelhante à disponível com o JDK, mas essa antiga suporta espaços reservados nomeados.

Ao contrário de outras soluções fornecidas nesta página. O ICU4j faz parte do projeto do ICU que é mantido pela IBM e atualizado regularmente. Além disso, ele suporta casos de uso avançados, como pluralização e muito mais.

Aqui está um exemplo de código:

MessageFormat messageFormat =
        new MessageFormat("Publication written by {author}.");

Map<String, String> args = Map.of("author", "John Doe");

System.out.println(messageFormat.format(args));
Laurent
fonte
0

Existe o Java Plugin para usar a interpolação de strings em Java (como no Kotlin, JavaScript). Suporte Java 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

Usando variáveis ​​em literais de string:

int a = 3;
int b = 4;
System.out.println("${a} + ${b} = ${a+b}");

Usando expressões:

int a = 3;
int b = 4;
System.out.println("pow = ${a * a}");
System.out.println("flag = ${a > b ? true : false}");

Usando funções:

@Test
void functionCall() {
    System.out.println("fact(5) = ${factorial(5)}");
}

long factorial(int n) {
    long fact = 1;
    for (int i = 2; i <= n; i++) {
        fact = fact * i;
    }
    return fact;
}

Para mais informações, leia o projeto README.

hermeslm
fonte