it-swarm-es.tech

¿Cuál es la mejor manera de construir una cadena de elementos delimitados en Java?

Mientras trabajaba en una aplicación Java, hace poco tuve que armar una lista de valores delimitada por comas para pasar a otro servicio web sin saber cuántos elementos habría por adelantado. Lo mejor que pude sacar de la cabeza fue algo como esto:

public String appendWithDelimiter( String original, String addition, String delimiter ) {
    if ( original.equals( "" ) ) {
        return addition;
    } else {
        return original + delimiter + addition;
    }
}

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Me doy cuenta de que esto no es particularmente eficiente, ya que hay cadenas que se están creando por todas partes, pero buscaba más claridad que optimización.

En Ruby, puedo hacer algo como esto, que se siente mucho más elegante:

parameterArray = [];
parameterArray << "elementName" if condition;
parameterArray << "anotherElementName" if anotherCondition;
parameterString = parameterArray.join(",");

Pero como Java carece de un comando de combinación, no pude averiguar nada equivalente.

Entonces, ¿cuál es la mejor manera de hacer esto en Java?

274
Sean McMains

Pre Java 8:

El amigo común de Apache es tu amigo aquí; proporciona un método de unión muy similar al que te refieres en Ruby:

StringUtils.join(Java.lang.Iterable,char)


Java 8:

Java 8 proporciona unirse fuera de la caja mediante StringJoiner y String.join(). Los fragmentos a continuación muestran cómo puedes usarlos:

StringJoiner

StringJoiner joiner = new StringJoiner(",");
joiner.add("01").add("02").add("03");
String joinedString = joiner.toString(); // "01,02,03"

String.join(CharSequence delimiter, CharSequence... elements))

String joinedString = String.join(" - ", "04", "05", "06"); // "04 - 05 - 06"

String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

List<String> strings = new LinkedList<>();
strings.add("Java");strings.add("is");
strings.add("cool");
String message = String.join(" ", strings);
//message returned is: "Java is cool"
482
Martin Gladdish

Podría escribir un pequeño método de utilidad de estilo de unión que funcione en Java.util.Lists

public static String join(List<String> list, String delim) {

    StringBuilder sb = new StringBuilder();

    String loopDelim = "";

    for(String s : list) {

        sb.append(loopDelim);
        sb.append(s);            

        loopDelim = delim;
    }

    return sb.toString();
}

Entonces utilízalo así:

    List<String> list = new ArrayList<String>();

    if( condition )        list.add("elementName");
    if( anotherCondition ) list.add("anotherElementName");

    join(list, ",");
51
Rob Dickerson

En el caso de Android, la clase StringUtils de commons no está disponible, así que para esto utilicé

Android.text.TextUtils.join(CharSequence delimiter, Iterable tokens)

http://developer.Android.com/reference/Android/text/TextUtils.html

44
Kevin

La biblioteca Guava de Google tienecom.google.common.base.Joinerclase que ayuda a resolver tales tareas.

Muestras:

"My pets are: " + Joiner.on(", ").join(Arrays.asList("rabbit", "parrot", "dog")); 
// returns "My pets are: rabbit, parrot, dog"

Joiner.on(" AND ").join(Arrays.asList("field1=1" , "field2=2", "field3=3"));
// returns "field1=1 AND field2=2 AND field3=3"

Joiner.on(",").skipNulls().join(Arrays.asList("London", "Moscow", null, "New York", null, "Paris"));
// returns "London,Moscow,New York,Paris"

Joiner.on(", ").useForNull("Team held a draw").join(Arrays.asList("FC Barcelona", "FC Bayern", null, null, "Chelsea FC", "AC Milan"));
// returns "FC Barcelona, FC Bayern, Team held a draw, Team held a draw, Chelsea FC, AC Milan"

Aquí hay un artículo sobre las utilidades de cadena de Guava .

31
Alex K

En Java 8 puedes usar String.join() :

List<String> list = Arrays.asList("foo", "bar", "baz");
String joined = String.join(" and ", list); // "foo and bar and baz"

También vea esta respuesta para ver un ejemplo de API Stream.

25
micha

Puedes generalizarlo, pero no hay ninguna combinación en Java, como bien dices.

Esto podría funcionar mejor.

public static String join(Iterable<? extends CharSequence> s, String delimiter) {
    Iterator<? extends CharSequence> iter = s.iterator();
    if (!iter.hasNext()) return "";
    StringBuilder buffer = new StringBuilder(iter.next());
    while (iter.hasNext()) buffer.append(delimiter).append(iter.next());
    return buffer.toString();
}
20
Vinko Vrsalovic

Utilice un enfoque basado en Java.lang.StringBuilder ! ("Una secuencia mutable de caracteres.")

Como mencionaste, todas esas concatenaciones de cadenas están creando cadenas por todas partes. StringBuilder no hará eso.

¿Por qué StringBuilder en lugar de StringBuffer ? Desde la StringBuilder javadoc:

Donde sea posible, se recomienda que esta clase se use con preferencia a StringBuffer, ya que será más rápida en la mayoría de las implementaciones.

15
Stu Thompson

en Java 8 puedes hacer esto como:

list.stream().map(Object::toString)
                        .collect(Collectors.joining(delimiter));

si la lista tiene nulos puede usar:

list.stream().map(String::valueOf)
                .collect(Collectors.joining(delimiter))
12
gladiator

Yo usaría Google Collections. Hay una instalación de Nice Join.
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html

Pero si quisiera escribirlo por mi cuenta,

package util;

import Java.util.ArrayList;
import Java.util.Iterable;
import Java.util.Collections;
import Java.util.Iterator;

public class Utils {
    // accept a collection of objects, since all objects have toString()
    public static String join(String delimiter, Iterable<? extends Object> objs) {
        if (objs.isEmpty()) {
            return "";
        }
        Iterator<? extends Object> iter = objs.iterator();
        StringBuilder buffer = new StringBuilder();
        buffer.append(iter.next());
        while (iter.hasNext()) {
            buffer.append(delimiter).append(iter.next());
        }
        return buffer.toString();
    }

    // for convenience
    public static String join(String delimiter, Object... objs) {
        ArrayList<Object> list = new ArrayList<Object>();
        Collections.addAll(list, objs);
        return join(delimiter, list);
    }
}

Creo que funciona mejor con una colección de objetos, ya que ahora no tienes que convertir tus objetos en cadenas antes de unirlos.

10
Eric Normand

Apache commons StringUtils class tiene un método de unión.

8
Alejandro

Java 8

stringCollection.stream().collect(Collectors.joining(", "));
4
gstackoverflow

Y una mínima (si no quieres incluir Apache Commons vs Guava en las dependencias del proyecto solo por unir cadenas)

/**
 *
 * @param delim : String that should be kept in between the parts
 * @param parts : parts that needs to be joined
 * @return  a String that's formed by joining the parts
 */
private static final String join(String delim, String... parts) {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < parts.length - 1; i++) {
        builder.append(parts[i]).append(delim);
    }
    if(parts.length > 0){
        builder.append(parts[parts.length - 1]);
    }
    return builder.toString();
}
3
Thamme Gowda

Usa StringBuilder y la clase Separator

StringBuilder buf = new StringBuilder();
Separator sep = new Separator(", ");
for (String each : list) {
    buf.append(sep).append(each);
}

Separador envuelve un delimitador. El delimitador se devuelve mediante el método toString de Separator, a menos que en la primera llamada que devuelva la cadena vacía.

Código fuente para la clase Separator

public class Separator {

    private boolean skipFirst;
    private final String value;

    public Separator() {
        this(", ");
    }

    public Separator(String value) {
        this.value = value;
        this.skipFirst = true;
    }

    public void reset() {
        skipFirst = true;
    }

    public String toString() {
        String sep = skipFirst ? "" : value;
        skipFirst = false;
        return sep;
    }

}
3
akuhn

Puede usar el tipo StringBuilder de Java para esto. También hay StringBuffer, pero contiene lógica de seguridad de subproceso adicional que a menudo es innecesaria.

3
Kent Boogaart

Si está utilizando Eclipse Collections , puede usar makeString() o appendString().

makeString() devuelve una representación String, similar a toString().

Tiene tres formas

  • makeString(start, separator, end)
  • makeString(separator) los valores predeterminados comienzan y terminan en cadenas vacías
  • makeString() por defecto el separador a ", " (coma y espacio)

Ejemplo de código:

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
assertEquals("[1/2/3]", list.makeString("[", "/", "]"));
assertEquals("1/2/3", list.makeString("/"));
assertEquals("1, 2, 3", list.makeString());
assertEquals(list.toString(), list.makeString("[", ", ", "]"));

appendString() es similar a makeString(), pero se agrega a una Appendable (como StringBuilder) y es void. Tiene las mismas tres formas, con un primer argumento adicional, el Anexable.

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
Appendable appendable = new StringBuilder();
list.appendString(appendable, "[", "/", "]");
assertEquals("[1/2/3]", appendable.toString());

Si no puede convertir su colección a un tipo de colecciones Eclipse, simplemente adáptela con el adaptador correspondiente.

List<Object> list = ...;
ListAdapter.adapt(list).makeString(",");

Nota: Soy un conmutador para las colecciones de Eclipse.

2
Craig P. Motlin

Si está utilizando Spring MVC, puede intentar los siguientes pasos.

import org.springframework.util.StringUtils;

List<String> groupIds = new List<String>;   
groupIds.add("a");    
groupIds.add("b");    
groupIds.add("c");

String csv = StringUtils.arrayToCommaDelimitedString(groupIds.toArray());

Resultará a a,b,c

2
Ankit Lalan

¿Por qué no escribir su propio método join ()? Tomaría como parámetros la colección de cadenas y una cadena delimitadora. Dentro del método iterar sobre la colección y construir su resultado en un StringBuffer.

2
Dave Costa

Con Java 5 args variable, por lo que no tiene que rellenar todas sus cadenas en una colección o matriz explícitamente:

import junit.framework.Assert;
import org.junit.Test;

public class StringUtil
{
    public static String join(String delim, String... strings)
    {
        StringBuilder builder = new StringBuilder();

        if (strings != null)
        {
            for (String str : strings)
            {
                if (builder.length() > 0)
                {
                    builder.append(delim).append(" ");
                }
                builder.append(str);
            }
        }           
        return builder.toString();
    }
    @Test
    public void joinTest()
    {
        Assert.assertEquals("", StringUtil.join(",", null));
        Assert.assertEquals("", StringUtil.join(",", ""));
        Assert.assertEquals("", StringUtil.join(",", new String[0]));
        Assert.assertEquals("test", StringUtil.join(",", "test"));
        Assert.assertEquals("foo, bar", StringUtil.join(",", "foo", "bar"));
        Assert.assertEquals("foo, bar, x", StringUtil.join(",", "foo", "bar", "x"));
    }
}
1
Mark B

Tipo nativo de Java 8

List<Integer> example;
example.add(1);
example.add(2);
example.add(3);
...
example.stream().collect(Collectors.joining(","));

Objeto personalizado de Java 8:

List<Person> person;
...
person.stream().map(Person::getAge).collect(Collectors.joining(","));
1
Luis Aguilar

Para aquellos que están en un contexto de Spring, su StringUtils class también es útil:

Hay muchos atajos útiles como:

  • collectionToCommaDelimitedString (Collection coll)
  • collectionToDelimitedString (Collection coll, String delim)
  • arrayToDelimitedString (Object [] arr, String delim)

y muchos otros.

Esto puede ser útil si no está utilizando Java 8 y ya está en un contexto de Spring.

Lo prefiero frente a Apache Commons (aunque también es muy bueno) para el soporte de Collection, que es más fácil así:

// Encoding Set<String> to String delimited 
String asString = org.springframework.util.StringUtils.collectionToDelimitedString(codes, ";");

// Decoding String delimited to Set
Set<String> collection = org.springframework.util.StringUtils.commaDelimitedListToSet(asString);
1
рüффп

Probablemente debería usar una StringBuilder con el método append para construir su resultado, pero de lo contrario, esta es una solución tan buena como la que ofrece Java.

1
newdayrising

¿Por qué no haces en Java lo mismo que estás haciendo en Ruby, que es crear la cadena separada del delimitador solo después de haber agregado todas las piezas a la matriz?

ArrayList<String> parms = new ArrayList<String>();
if (someCondition) parms.add("someString");
if (anotherCondition) parms.add("someOtherString");
// ...
String sep = ""; StringBuffer b = new StringBuffer();
for (String p: parms) {
    b.append(sep);
    b.append(p);
    sep = "yourDelimiter";
}

Es posible que desee mover ese bucle for en un método de ayuda independiente, y también usar StringBuilder en lugar de StringBuffer ...

Editar : fija el orden de los apéndices.

1
agnul

Así que básicamente algo como esto:

public static String appendWithDelimiter(String original, String addition, String delimiter) {

if (original.equals("")) {
    return addition;
} else {
    StringBuilder sb = new StringBuilder(original.length() + addition.length() + delimiter.length());
        sb.append(original);
        sb.append(delimiter);
        sb.append(addition);
        return sb.toString();
    }
}
0
killdash10

No sé si esto es realmente mejor, pero al menos está usando StringBuilder, que puede ser un poco más eficiente.

Abajo hay un enfoque más genérico si puede construir la lista de parámetros ANTES de realizar cualquier delimitación de parámetros.

// Answers real question
public String appendWithDelimiters(String delimiter, String original, String addition) {
    StringBuilder sb = new StringBuilder(original);
    if(sb.length()!=0) {
        sb.append(delimiter).append(addition);
    } else {
        sb.append(addition);
    }
    return sb.toString();
}


// A more generic case.
// ... means a list of indeterminate length of Strings.
public String appendWithDelimitersGeneric(String delimiter, String... strings) {
    StringBuilder sb = new StringBuilder();
    for (String string : strings) {
        if(sb.length()!=0) {
            sb.append(delimiter).append(string);
        } else {
            sb.append(string);
        }
    }

    return sb.toString();
}

public void testAppendWithDelimiters() {
    String string = appendWithDelimitersGeneric(",", "string1", "string2", "string3");
}
0
Mikezx6r

Su enfoque no es tan malo, pero debe usar un StringBuffer en lugar de usar el signo +. El + tiene la gran desventaja de que se está creando una nueva instancia de String para cada operación individual. Cuanto más larga sea la cuerda, mayor será la sobrecarga. Así que usar un StringBuffer debería ser la forma más rápida:

public StringBuffer appendWithDelimiter( StringBuffer original, String addition, String delimiter ) {
        if ( original == null ) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(addition);
                return buffer;
        } else {
                buffer.append(delimiter);
                buffer.append(addition);
                return original;
        }
}

Una vez que haya terminado de crear su cadena, simplemente llame a toString () en el StringBuffer devuelto.

0
Yaba
//Note: if you have access to Java5+, 
//use StringBuilder in preference to StringBuffer.  
//All that has to be replaced is the class name.  
//StringBuffer will work in Java 1.4, though.

appendWithDelimiter( StringBuffer buffer, String addition, 
    String delimiter ) {
    if ( buffer.length() == 0) {
        buffer.append(addition);
    } else {
        buffer.append(delimiter);
        buffer.append(addition);
    }
}


StringBuffer parameterBuffer = new StringBuffer();
if ( condition ) { 
    appendWithDelimiter(parameterBuffer, "elementName", "," );
}
if ( anotherCondition ) {
    appendWithDelimiter(parameterBuffer, "anotherElementName", "," );
}

//Finally, to return a string representation, call toString() when returning.
return parameterBuffer.toString(); 
0
MetroidFan2002

usando Dollar es tan simple como escribir:

String joined = $(aCollection).join(",");

NB: funciona también para Array y otros tipos de datos

Implementación

Internamente utiliza un truco muy bueno:

@Override
public String join(String separator) {
    Separator sep = new Separator(separator);
    StringBuilder sb = new StringBuilder();

    for (T item : iterable) {
        sb.append(sep).append(item);
    }

    return sb.toString();
}

la clase Separator devuelve la Cadena vacía solo la primera vez que se invoca, luego devuelve el separador:

class Separator {

    private final String separator;
    private boolean wasCalled;

    public Separator(String separator) {
        this.separator = separator;
        this.wasCalled = false;
    }

    @Override
    public String toString() {
        if (!wasCalled) {
            wasCalled = true;
            return "";
        } else {
            return separator;
        }
    }
}
0
dfa

Arreglar respuesta a Rob Dickerson.

Es más fácil de usar:

public static String join(String delimiter, String... values)
{
    StringBuilder stringBuilder = new StringBuilder();

    for (String value : values)
    {
        stringBuilder.append(value);
        stringBuilder.append(delimiter);
    }

    String result = stringBuilder.toString();

    return result.isEmpty() ? result : result.substring(0, result.length() - 1);
}
0
maXp

En lugar de usar la concatenación de cadenas, debe usar StringBuilder si su código no está enlazado y StringBuffer si lo está.

0
Aaron

Por lo tanto, puede hacer un par de cosas para tener la sensación de que parece que está buscando:

1) Extienda la clase de lista - y agregue el método de unión a ella. El método de unión simplemente haría el trabajo de concatenar y agregar el delimitador (que podría ser un parámetro para el método de unión)

2) Parece que Java 7 va a agregar métodos de extensión a Java, lo que le permite simplemente adjuntar un método específico a una clase: así podría escribir ese método de unión y agregarlo como un método de extensión a la Lista o incluso a Colección.

La solución 1 es probablemente la única realista ahora, aunque como Java 7 aún no está disponible :) Pero debería funcionar bien.

Para usar ambos, simplemente debe agregar todos sus artículos a la Lista o la Colección como de costumbre, y luego llamar al nuevo método personalizado para 'unirse' a ellos.

0
user13276

Estás haciendo esto un poco más complicado de lo que tiene que ser. Comencemos con el final de su ejemplo:

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Con el pequeño cambio de usar un StringBuilder en lugar de un String, esto se convierte en:

StringBuilder parameterString = new StringBuilder();
if (condition) parameterString.append("elementName").append(",");
if (anotherCondition) parameterString.append("anotherElementName").append(",");
...

Cuando hayas terminado (supongo que también debes comprobar algunas otras condiciones), asegúrate de eliminar la coma de relaves con un comando como este:

if (parameterString.length() > 0) 
    parameterString.deleteCharAt(parameterString.length() - 1);

Y finalmente, consigue la cuerda con la que quieres

parameterString.toString();

También puede reemplazar el "," en la segunda llamada para adjuntar con una cadena delimitadora genérica que se puede establecer en cualquier cosa. Si tiene una lista de cosas que sabe que necesita adjuntar (no condicional), puede poner este código dentro de un método que toma una lista de cadenas.

0
cjw2600

Ligera mejora [velocidad] de la versión de izb:

public static String join(String[] strings, char del)
{
    StringBuilder sb = new StringBuilder();
    int len = strings.length;

    if(len > 1) 
    {
       len -= 1;
    }else
    {
       return strings[0];
    }

    for (int i = 0; i < len; i++)
    {
       sb.append(strings[i]).append(del);
    }

    sb.append(strings[i]);

    return sb.toString();
}
0
java al

Personalmente, a menudo utilizo la siguiente solución simple para fines de registro:

List lst = Arrays.asList("ab", "bc", "cd");
String str = lst.toString().replaceAll("[\\[\\]]", "");
0
Philipp Grigoryev

Puedes probar algo como esto:

StringBuilder sb = new StringBuilder();
if (condition) { sb.append("elementName").append(","); }
if (anotherCondition) { sb.append("anotherElementName").append(","); }
String parameterString = sb.toString();
0
martinatime