Marcadores de posición con nombre en formato de cadena


175

En Python, al formatear una cadena, puedo llenar marcadores de posición por nombre en lugar de por posición, así:

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

Me pregunto si eso es posible en Java (con suerte, sin bibliotecas externas).


Puede extender MessageFormat e implementar la funcionalidad de mapeo de variables a índices en eso.
vpram86


1
Algo de historia: Java copió principalmente C / C ++ sobre este asunto, ya que trató de atraer a los desarrolladores del mundo de C ++, donde %sera una práctica común. en.wikipedia.org/wiki/Printf_format_string#History También tenga en cuenta que algunos IDE y FindBugs pueden detectar automáticamente los recuentos% s y% d que no coinciden, pero aún así preferiría los campos con nombre.
Christophe Roussy

Respuestas:


143

StrSubstitutor of jakarta commons lang es una forma ligera de hacerlo siempre que sus valores ya estén formateados correctamente.

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

Los resultados anteriores en:

"Hay un valor incorrecto '1' en la columna # 2"

Cuando use Maven, puede agregar esta dependencia a su pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

2
Me pareció decepcionante que la biblioteca no arroje si no se encuentran claves, sin embargo, si usa la sintaxis predeterminada ( ${arg}) en lugar de la personalizada anterior ( %(arg)), la expresión regular no se compilará, que es el efecto deseado.
John Lehmann

2
Puede establecer un VariableResolver personalizado y lanzar una excepción si la clave no está presente en el mapa.
Mene

77
Hilo antiguo, pero a partir de 3.6, el paquete de texto fue desaprobado en favor de commons-text. commons.apache.org/proper/commons-text
Jeff Walker

74

no del todo, pero puede usar MessageFormat para hacer referencia a un valor varias veces:

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

Lo anterior también se puede hacer con String.format (), pero encuentro limpiador de sintaxis messageFormat si necesita construir expresiones complejas, además no necesita preocuparse por el tipo de objeto que está poniendo en la cadena


No estoy seguro de por qué no puede hacerlo, la posición en la cadena no es importante, solo la posición en la lista de argumentos, lo que lo convierte en un problema de cambio de nombre. Conoce el nombre de las claves, lo que significa que puede decidir una posición para una clave en la lista de argumentos. de ahora en adelante, el valor se conocerá como 0 y la columna como 1: MessageeFormat.format ("Hay un valor incorrecto \" {0} \ "en la columna # {1}, usar {0} como valor puede causar muchos problemas", valueMap .get ('valor'), valueMap.get ('columna'));
giladbu

1
Gracias por una pista, me ayudó a escribir una función simple que hace exactamente lo que quiero (lo puse a continuación).
Andy

1
De acuerdo, la sintaxis es mucho más limpia. Lástima que MessageFormat tenga una mente propia cuando se trata de formatear valores numéricos.
Kees de Kooter

Y parece ignorar los marcadores de posición rodeados de comillas simples.
Kees de Kooter

MessageFormates genial pero engorroso para contenido json relativamente grande
EliuX

32

Otro ejemplo de Apache Common StringSubstitutor para marcador de posición simple nombrado.

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.

Si espera cargar archivos muy grandes, descubrí que esta biblioteca también admite replaceInvalores sustitutivos en un búfer: StringBuilder o TextStringBuilder. Con este enfoque, todo el contenido del archivo no se cargará en la memoria.
Edward Corrigall

15

Puede usar la biblioteca StringTemplate , ofrece lo que desea y mucho más.

import org.antlr.stringtemplate.*;

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

Tuve problemas con el 'unexpected char: '''
personaje

11

Para casos muy simples , simplemente puede usar un reemplazo de cadena codificado, sin necesidad de una biblioteca allí:

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

ADVERTENCIA : solo quería mostrar el código más simple posible. Por supuesto, NO use esto para un código de producción serio donde la seguridad es importante, como se indica en los comentarios: el escape, el manejo de errores y la seguridad son un problema aquí. Pero en el peor de los casos, ahora sabe por qué se requiere el uso de una 'buena' lib :-)


1
Este es simple y fácil, pero la desventaja es que falla en silencio cuando no se encontró el valor. Simplemente deja el marcador de posición en la cadena original.
kiedysktos

@kiedysktos, se podría mejorar en él al hacer un cheque, pero si usted quiere la cosa completa, utilice un lib :)
Christophe Roussy

2
Advertencia: Debido a que esta técnica trata los resultados de sustitución intermedia como cadenas de formato propias, esta solución es vulnerable a los ataques de cadenas de formato . Cualquier solución correcta debe hacer una sola pasada a través de la cadena de formato.
200_success

@ 200_success Sí, es bueno hablar de seguridad, por supuesto, este código no es para uso serio de producción ...
Christophe Roussy

8

¡Gracias por toda tu ayuda! Usando todas sus pistas, he escrito la rutina para hacer exactamente lo que quiero: formateo de cadena similar a python usando el diccionario. Como soy novato en Java, cualquier sugerencia es apreciada.

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

A diferencia de la respuesta de Lombo, esto no puede atascarse en un bucle infinito, ya formatPosque no puede contener formatKey.
Aaron Dufour el

66
Advertencia: Debido a que el bucle trata los resultados de sustitución intermedia como cadenas de formato propias, esta solución es vulnerable a los ataques de cadenas de formato . Cualquier solución correcta debe hacer una sola pasada a través de la cadena de formato.
200_success

6

Este es un hilo antiguo, pero solo para el registro, también podría usar el estilo Java 8, como este:

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

La salida 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.

Esto es brillante, pero lamentablemente viola las especificaciones aquí docs.oracle.com/javase/8/docs/api/java/util/stream/… La función de combinación debe devolver el segundo parámetro si el primer parámetro es la identidad. El anterior devolvería la identidad en su lugar. También viola esta regla: combiner.apply (u, acumulator.apply (identidad, t)) == acumulator.apply (u, t)
Ali Cheaito

Interesante ... pero solo si propone una mejor manera de pasar el mapa, también si es posible después de la plantilla, como la mayoría de los códigos de formato.
Christophe Roussy

44
Advertencia: Debido a que .reduce()trata los resultados de sustitución intermedia como cadenas de formato propias, esta solución es vulnerable a los ataques de cadenas de formato . Cualquier solución correcta debe hacer una sola pasada a través de la cadena de formato.
200_success

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

Ejemplo:

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.

2
Esta debería ser la respuesta, ya que evita los ataques de cadenas de formato a los que la mayoría de las otras soluciones son vulnerables. Tenga en cuenta que Java 9 lo hace mucho más simple, con soporte para .replaceAll()devoluciones de llamada de sustitución de cadenas .
200_success

Esta debería ser la respuesta, ya que no utiliza ninguna biblioteca externa.
Bohao LI

3

Soy el autor de una pequeña biblioteca que hace exactamente lo que quieres:

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

O puedes encadenar los argumentos:

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

// Output: "5 + 10 = 15"

¿Es posible hacer un formateo basado en condiciones con su biblioteca?
gaurav

@gaurav no del todo. Si lo necesita, necesita una biblioteca de plantillas con todas las funciones.
Andrei Ciobanu

2

El método replaceEach de Apache Commons Lang puede ser útil dependiendo de sus necesidades específicas. Puede usarlo fácilmente para reemplazar marcadores de posición por nombre con esta llamada de método único:

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

Dado un poco de texto de entrada, esto reemplazará todas las apariciones de los marcadores de posición en el primer conjunto de cadenas con los valores correspondientes en el segundo.


1

Podría tener algo como esto en una clase auxiliar de cadena

/**
 * 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();
}

Muchas gracias, hace casi lo que quiero, pero lo único es que no tiene en cuenta los modificadores (considere "% (key) 08d")
Andy

1
Tenga en cuenta también que esto entra en un bucle infinito si alguno de los valores utilizados contiene la entrada correspondiente.
Aaron Dufour el

1
Advertencia: Debido a que el bucle trata los resultados de sustitución intermedia como cadenas de formato propias, esta solución es vulnerable a los ataques de cadenas de formato . Cualquier solución correcta debe hacer una sola pasada a través de la cadena de formato.
200_success

1

Mi respuesta es:

a) use StringBuilder cuando sea posible

b) mantenga la posición del "marcador de posición" (en cualquier forma: entero es el mejor, especialmente como macro de dólar, etc.) y luego use StringBuilder.insert()(pocas versiones de argumentos).

El uso de bibliotecas externas parece excesivo y creo que el rendimiento de degradación es significativo, cuando StringBuilder se convierte a String internamente.


1

Según la respuesta , creé la MapBuilderclase:

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

}

Luego creé la clase StringFormatpara el formato de cadena:

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 podrías usar así:

String bookingDate = StringFormat.format("From $startDate to $endDate"), 
        "$startDate", formattedStartDate, 
        "$endDate", formattedEndDate
);

1
Advertencia: Debido a que el bucle trata los resultados de sustitución intermedia como cadenas de formato propias, esta solución es vulnerable a los ataques de cadenas de formato . Cualquier solución correcta debe hacer una sola pasada a través de la cadena de formato.
200_success

1

También creé una clase util / helper (usando jdk 8) que puede formatear una cadena y reemplaza las ocurrencias de variables.

Para este propósito, utilicé el método "appendReplacement" de Matchers que realiza todas las sustituciones y bucles solo sobre las partes afectadas de una cadena de formato.

La clase auxiliar actualmente no está bien documentada por javadoc. Cambiaré esto en el futuro;) De todos modos, comenté las líneas más 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;
    }
}

Puede crear una instancia de clase para un Mapa específico con valores (o sufijo prefijo o 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?"

Además, puede definir qué sucede si falta una clave en el mapa de valores (funciona en ambos sentidos, por ejemplo, nombre de variable incorrecto en la cadena de formato o clave faltante en el mapa). El comportamiento predeterminado es una excepción no marcada (sin marcar porque uso la función jdk8 predeterminada que no puede manejar excepciones marcadas) 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.

Puede definir un comportamiento personalizado en la llamada del constructor 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!"

o delegarlo de nuevo al comportamiento predeterminado sin clave:

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

Para un mejor manejo también hay representaciones de métodos estáticos 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!"

1

No hay nada integrado en Java al momento de escribir esto. Sugeriría escribir su propia implementación. Mi preferencia es una interfaz de generador de fluidos simple en lugar de crear un mapa y pasarlo a la función; terminas con un buen fragmento de código contiguo, por ejemplo:

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

Aquí hay una implementación simple:

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

0

Prueba Freemarker , biblioteca de plantillas.

texto alternativo


44
Freemarker? Supongo que está dispuesto a saber cómo hacer esto en Java. De todos modos, si Freemarker es la respuesta probable, ¿puedo decir que JSP también será la respuesta correcta?
Rakesh Juyal

1
Gracias, pero para mi tarea en cuestión parece una exageración. Pero gracias.
Andy

1
@Rakesh JSP es algo muy específico de "vista / FE". He usado FreeMarker en el pasado para generar XML y, a veces, incluso he generado archivos JAVA. Andy me temo que tendrá que escribir una utilidad usted mismo (o como la prescrita anteriormente)
Kannan Ekanath

@Boris, ¿cuál es mejor marcador libre vs velocidad vs plantilla de cadena?
gaurav



0

Deberías echar un vistazo a la biblioteca oficial ICU4J . Proporciona un MessageFormat clase similar a la disponible con el JDK, pero esta primera admite marcadores de posición con nombre.

A diferencia de otras soluciones proporcionadas en esta página. ICU4j es parte del proyecto ICU que mantiene IBM y lo actualiza regularmente. Además, admite casos de uso avanzados como la pluralización y mucho más.

Aquí hay un ejemplo 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));

0

Hay Java Plugin para usar la interpolación de cadenas en Java (como en Kotlin, JavaScript). Es compatible con Java 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

Usar variables en literales de cadena:

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

Usando expresiones:

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

Usando funciones:

@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 obtener más información, lea el proyecto README.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.