¿La forma más sofisticada de crear cadenas separadas por comas a partir de una colección / matriz / lista?

98

Durante mi trabajo con bases de datos, noté que escribo cadenas de consulta y en estas cadenas tengo que poner varias restricciones en la cláusula where de una lista / matriz / colección. Debería verse así:

select * from customer 
where customer.id in (34, 26, ..., 2);

Puede simplificar esto reduciendo esto a la pregunta de que tiene una colección de cadenas y desea crear una lista separada por comas de estas cadenas en una sola cadena.

Mi enfoque que he usado hasta ahora es algo así:

String result = "";
boolean first = true;
for(String string : collectionOfStrings) {
    if(first) {
        result+=string;
        first=false;
    } else {
        result+=","+string;
    }
}

Pero esto es como puede ver muy feo. No puede ver lo que sucede allí a primera vista, especialmente cuando las cadenas construidas (como todas las consultas SQL) se vuelven complicadas.

¿Cuál es tu forma (más) elegante?

maerch
fuente
Es de suponer que el SQL que se muestra arriba debería verse así: seleccione * de cliente donde el cliente.id en (34, 26, 2);
Dónal
Hay una parte complicada, cuando los elementos de la lista (cadenas) contienen comas o comillas dobles y deben escaparse con comillas. Si no me perdí nada, los ejemplos anteriores no lo consideran y detesto la idea de recorrer todos los textos y buscar comas. ¿Crees que hay una mejor manera de resolver esto?
Samurai Girl
verifique esta respuesta ... stackoverflow.com/a/15815631/728610
Arvind Sridharan
¿Alguna vez ha revisado stackoverflow.com/questions/10850753/… ?
Hiren Patel
Esto debería ser suficiente. stackoverflow.com/a/15815631/3157062
Parag Jadhav

Respuestas:

85

Nota: Esta respuesta era buena cuando se escribió hace 11 años, pero ahora hay opciones mucho mejores para hacer esto de manera más limpia en una sola línea, tanto usando solo clases integradas de Java como usando una biblioteca de utilidades. Vea otras respuestas a continuación.


Dado que las cadenas son inmutables, es posible que desee utilizar la clase StringBuilder si va a alterar la cadena en el código.

La clase StringBuilder puede verse como un objeto String mutable que asigna más memoria cuando se modifica su contenido.

La sugerencia original en la pregunta se puede escribir aún más clara y eficientemente, cuidando la coma final redundante :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";
gimel
fuente
7
Tenga en cuenta que esto requiere que su colección tenga al menos un elemento.
Guus
3
Ver la respuesta más votada - code.google.com/p/guava-libraries/wiki/StringsExplained
gimel
Consulte la solución sugerida para una lista vacía.
gimel
1
la respuesta de guayaba es mejor. No hay necesidad de reinventar la rueda.
Davidjnelson
1
@ xtreme-biker Con un compilador razonablemente moderno, StringBuilder podría usarse automáticamente. Verifique su entorno antes de usar + =. Ver stackoverflow.com/questions/1532461/…
gimel
76

Solo miré el código que hizo esto hoy. Esta es una variación de la respuesta de AviewAnew.

collectionOfStrings = /* source string collection */;
String csList = StringUtils.join(collectionOfStrings.toArray(), ",");

El StringUtils (<- commons.lang 2.x, o el enlace commons.lang 3.x ) que usamos es de Apache Commons .

Salmo del ogro 33
fuente
... y de donde viene StringUtils?
vwegert
1
Ah, buen punto. Ha pasado un tiempo desde que miré ese código, pero creo que estábamos usando org.apache.commons.lang.StringUtils.
Ogre Psalm33
Aquí hay un enlace en vivo al método de unión de StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/…
Ryan S
2
Genial gracias. StringUtils # join también funciona en un Iterable, por lo que probablemente no sea necesario convertir su colección en una matriz primero.
Roy
47

La forma en que escribo ese bucle es:

StringBuilder buff = new StringBuilder();
String sep = "";
for (String str : strs) {
    buff.append(sep);
    buff.append(str);
    sep = ",";
}
return buff.toString();

No se preocupe por el rendimiento de sep. Una tarea es muy rápida. Hotspot tiende a desprenderse de la primera iteración de un bucle de todos modos (ya que a menudo tiene que lidiar con rarezas como comprobaciones de alineación nula y mono / bimórfica).

Si lo usa mucho (más de una vez), colóquelo en un método compartido.

Hay otra pregunta sobre stackoverflow que trata sobre cómo insertar una lista de identificadores en una declaración SQL.

Tom Hawtin - tackline
fuente
42

Desde Java 8, puede usar:

Abdull
fuente
3
¡Esto es bueno! Si está manipulando objetos que necesitan una conversión de cadena especial no cubierta por toString (), reemplace Object :: toString con una java.util.function.Function <YourType, String> que mapea su clase a una String.
Torben
2
Además, puede usarlo así: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));para una sola variable de su colección.
numsu
Me pregunto sobre el desempeño de eso stream. Para int [] o long [] u otras matrices en las que el valor simplemente se puede convertir String, buscaría una solución que no sea de transmisión. De hecho estoy mirando.
Adam
11

Encontré el lenguaje del iterador elegante, porque tiene una prueba para más elementos (prueba nula / vacía omitida por brevedad):

public static String convert(List<String> list) {
    String res = "";
    for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
        res += iterator.next() + (iterator.hasNext() ? "," : "");
    }
    return res;
}
Miguel Ping
fuente
... y probablemente menos eficiente que la solución aceptada, dependiendo de qué tan compleja sea la llamada 'hasNext ()'. Además, probablemente debería utilizar un StringBuilder en lugar de una concatenación de cadenas.
Stephen C
Bien, si quieres ser exigente con la eficiencia, usa StringWriter;)
Miguel Ping
8

Hay muchas soluciones manuales para esto, pero quería reiterar y actualizar la respuesta de Julie anterior. Utilice la clase Joiner de colecciones de Google .

Joiner.on(", ").join(34, 26, ..., 2)

Maneja var args, iterables y matrices y maneja adecuadamente los separadores de más de un char (a diferencia de la respuesta de gimmel). También manejará valores nulos en su lista si lo necesita.

caso nelson
fuente
7

Aquí hay una versión increíblemente genérica que he creado a partir de una combinación de las sugerencias anteriores:

public static <T> String buildCommaSeparatedString(Collection<T> values) {
    if (values==null || values.isEmpty()) return "";
    StringBuilder result = new StringBuilder();
    for (T val : values) {
        result.append(val);
        result.append(",");
    }
    return result.substring(0, result.length() - 1);
}
Jeff
fuente
7
String.join(", ", collectionOfStrings)

disponible en la api de Java8.

alternativa a (sin la necesidad de agregar una dependencia de guayaba de google):

Joiner.on(",").join(collectionOfStrings);
Robjwilkins
fuente
5

Tu podrías intentar

List collections = Arrays.asList(34, 26, "...", 2);
String asString = collection.toString();
// justValues = "34, 26, ..., 2"
String justValues = asString.substring(1, asString.length()-1);
Peter Lawrey
fuente
4

Esta será la solución más corta hasta ahora, excepto el uso de Guava o Apache Commons.

String res = "";
for (String i : values) {
    res += res.isEmpty() ? i : ","+i;
}

Bueno con 0,1 y n lista de elementos. Pero deberá verificar la lista nula. Yo uso esto en GWT, así que estoy bien sin StringBuilder allí. Y para listas cortas con solo un par de elementos, también está bien en otros lugares;)

tomo
fuente
4

En caso de que alguien haya tropezado con esto en tiempos más recientes, he agregado una variación simple usando Java 8 reduce(). También incluye algunas de las soluciones ya mencionadas por otros:

import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang.StringUtils;    

import com.google.common.base.Joiner;

public class Dummy {
  public static void main(String[] args) {

    List<String> strings = Arrays.asList("abc", "de", "fg");
    String commaSeparated = strings
        .stream()
        .reduce((s1, s2) -> {return s1 + "," + s2; })
        .get();

    System.out.println(commaSeparated);

    System.out.println(Joiner.on(',').join(strings));

    System.out.println(StringUtils.join(strings, ","));

  }
}
Christof
fuente
4

En Android deberías usar esto:

TextUtils.join(",",collectionOfStrings.toArray());
Pascalio
fuente
4

Creo que no es una buena idea construir el sql concatenando los valores de la cláusula where como lo estás haciendo:

SELECT.... FROM.... WHERE ID IN( value1, value2,....valueN)

De dónde valueXviene una lista de Strings.

Primero, si está comparando cadenas, deben estar entre comillas, y esto no es trivial si las cadenas pudieran tener una cita dentro.

En segundo lugar, si los valores provienen del usuario u otro sistema, entonces es posible un ataque de inyección SQL.

Es mucho más detallado, pero lo que debes hacer es crear una cadena como esta:

SELECT.... FROM.... WHERE ID IN( ?, ?,....?)

y luego unir las variables con Statement.setString(nParameter,parameterValue).

Telcontar
fuente
3

Solo otro método para lidiar con este problema. No es el más corto, pero es eficiente y hace el trabajo.

/**
 * Creates a comma-separated list of values from given collection.
 * 
 * @param <T> Value type.
 * @param values Value collection.
 * @return Comma-separated String of values.
 */
public <T> String toParameterList(Collection<T> values) {
   if (values == null || values.isEmpty()) {
      return ""; // Depending on how you want to deal with this case...
   }
   StringBuilder result = new StringBuilder();
   Iterator<T> i = values.iterator();
   result.append(i.next().toString());
   while (i.hasNext()) {
      result.append(",").append(i.next().toString());
   }
   return result.toString();
}
silverminken
fuente
2

Hay algunas bibliotecas Java de terceros que proporcionan el método de unión de cadenas, pero probablemente no desee comenzar a usar una biblioteca solo para algo tan simple como eso. Simplemente crearía un método auxiliar como este, que creo que es un poco mejor que su versión. Utiliza StringBuffer, que será más eficiente si necesita unir muchas cadenas, y funciona en una colección de cualquier tipo.

public static <T> String join(Collection<T> values)
{
    StringBuffer ret = new StringBuffer();
    for (T value : values)
    {
        if (ret.length() > 0) ret.append(",");
        ret.append(value);
    }
    return ret.toString();
}

Otra sugerencia con el uso de Collection.toString () es más corta, pero se basa en Collection.toString () que devuelve una cadena en un formato muy específico, en el que personalmente no querría confiar.

Denis Fradlin
fuente
2

Si usa Spring, puede hacer:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(paquete org.springframework.util)

fines de semana
fuente
1

No estoy seguro de cuán "sofisticado" es esto, pero ciertamente es un poco más corto. Funcionará con varios tipos diferentes de colección, por ejemplo, Set <Integer>, List <String>, etc.

public static final String toSqlList(Collection<?> values) {

    String collectionString = values.toString();

    // Convert the square brackets produced by Collection.toString() to round brackets used by SQL
    return "(" + collectionString.substring(1, collectionString.length() - 1) + ")";
}

Ejercicio para el lector : modifique este método para que maneje correctamente una colección nula / vacía :)

Dónal
fuente
1

Lo que hace que el código sea feo es el manejo especial para el primer caso. La mayoría de las líneas de este pequeño fragmento están dedicadas, no a realizar el trabajo de rutina del código, sino a manejar ese caso especial. Y eso es lo que resuelven alternativas como la de Gimel, al mover el manejo especial fuera del bucle. Hay un caso especial (bueno, podría ver el inicio y el final como casos especiales, pero solo uno de ellos debe tratarse de manera especial), por lo que manejarlo dentro del ciclo es innecesariamente complicado.

Carl Manaster
fuente
1

Acabo de registrar una prueba por el dólar de mi biblioteca :

@Test
public void join() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    String string = $(list).join(",");
}

es crear una envoltura alrededor de fluidez listas / arreglos / cuerdas / etc usando una sola importación estática : $.

NB :

usando rangos, la lista anterior se puede reescribir como $(1, 5).join(",")

dfa
fuente
1

Lo bueno de la expresión IN es que si tiene valores repetidos, no cambia el resultado. Entonces, simplemente duplique el primer elemento y procese la lista completa. Esto supone que hay al menos un elemento en la lista. Si no hay elementos, sugeriría verificarlo primero y luego no ejecutar el SQL en absoluto.

Esto hará el truco, es obvio en lo que está haciendo y no depende de ninguna biblioteca externa:

StringBuffer inString = new StringBuffer(listOfIDs.get(0).toString());
for (Long currentID : listOfIDs) {
  inString.append(",").append(currentID);
}
EMPUJE
fuente
1

Si bien creo que su mejor opción es usar Joiner de Guava, si tuviera que codificarlo a mano, este enfoque me parece más elegante que la 'primera' bandera o cortar la última coma.

private String commas(Iterable<String> strings) {
    StringBuilder buffer = new StringBuilder();
    Iterator<String> it = strings.iterator();
    if (it.hasNext()) {
        buffer.append(it.next());
        while (it.hasNext()) {
            buffer.append(',');
            buffer.append(it.next());
        }
    }

    return buffer.toString();
}
Víctor
fuente
1

si tiene una matriz, puede hacer:

Arrays.asList(parameters).toString()
clima nublado
fuente
1

Otra opción, basada en lo que veo aquí (con ligeras modificaciones).

public static String toString(int[] numbers) {
    StringBuilder res = new StringBuilder();
    for (int number : numbers) {
        if (res.length() != 0) {
            res.append(',');
        }
        res.append(number);
    }
    return res.toString();
}
elcuco
fuente
1

Los 'métodos' de unión están disponibles en Arrays y las clases que se extienden AbstractCollectionspero no anulan el toString()método (como prácticamente todas las colecciones java.util).

Por ejemplo:

String s= java.util.Arrays.toString(collectionOfStrings.toArray());
s = s.substing(1, s.length()-1);// [] are guaranteed to be there

Esa es una forma bastante extraña, ya que solo funciona para números similares a datos SQL.

xss
fuente
1
List<String> collectionOfStrings = // List of string to concat
String csvStrings = StringUtils.collectionToDelimitedString(collectionOfStrings, ",");

StringUtils de springframeowrk: spring-core

Sridhar
fuente
0
java.util.List<String> lista = new java.util.ArrayList<String>();
lista.add("Hola");
lista.add("Julio");
System.out.println(lista.toString().replace('[','(').replace(']',')'));

$~(Hola, Julio)
Julio Cesar
fuente
1
Esta es una mala práctica. No puede suponer que la implementación de toString cambia.
drindt
0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

La representación de cadena consiste en una lista de los elementos de la colección en el orden en que son devueltos por su iterador, entre corchetes ("[]"). Los elementos adyacentes están separados por los caracteres "," (coma y espacio).

AbstractCollection javadoc

Todd Gatts
fuente
0

Token de lista = new ArrayList (resultado); constructor final de StringBuilder = new StringBuilder ();

    for (int i =0; i < tokens.size(); i++){
        builder.append(tokens.get(i));
        if(i != tokens.size()-1){
            builder.append(TOKEN_DELIMITER);
        }
    }

constructor.toString ();

UPS
fuente