Il modo più sofisticato per creare stringhe separate da virgole da una raccolta / matrice / elenco?


98

Durante il mio lavoro con i database ho notato che scrivo stringhe di query e in queste stringhe devo mettere diverse restrizioni nella clausola where da una lista / matrice / raccolta. Dovrebbe assomigliare a questo:

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

Puoi semplificarlo riducendo questo alla domanda che hai una raccolta di stringhe e vuoi creare un elenco separato da virgole di queste stringhe in una sola stringa.

Il mio approccio che ho usato finora è qualcosa del genere:

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

Ma questo è come puoi vedere molto brutto. Non puoi vedere cosa succede lì al primo sguardo, specialmente quando le stringhe costruite (come ogni query SQL) si complicano.

Qual è il tuo modo (più) elegante?


Presumibilmente l'SQL mostrato sopra dovrebbe effettivamente assomigliare a questo: select * from customer where customer.id in (34, 26, 2);
Dónal

C'è una parte complicata, quando gli elementi dell'elenco (stringhe) contengono virgole o virgolette doppie e devono essere preceduti da virgolette. Se non mi sono perso nulla, gli esempi sopra non lo considerano e odio l'idea di scorrere tutti i testi e cercare le virgole .. Pensi che ci sia un modo migliore per risolvere questo problema?
Samurai Girl

controllare questo out risposta ... stackoverflow.com/a/15815631/728610
Arvind Sridharan


Risposte:


85

Nota: questa risposta era buona quando è stata scritta 11 anni fa, ma ora ci sono opzioni di gran lunga migliori per farlo in modo più pulito in una singola riga, sia utilizzando solo classi integrate Java che utilizzando una libreria di utilità. Vedi altre risposte di seguito.


Poiché le stringhe sono immutabili, potresti voler utilizzare la classe StringBuilder se intendi modificare la stringa nel codice.

La classe StringBuilder può essere vista come un oggetto String mutabile che alloca più memoria quando il suo contenuto viene alterato.

Il suggerimento originale nella domanda può essere scritto in modo ancora più chiaro ed efficiente, facendo attenzione alla virgola finale ridondante :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";

7
Tieni presente che ciò richiede che la tua raccolta abbia almeno un elemento.
Guus


Vedere la correzione suggerita per un elenco vuoto.
Gimel

1
la risposta guava è migliore. Non c'è bisogno di reinventare la ruota.
davidjnelson

1
@ xtreme-biker Con un compilatore ragionevolmente moderno, StringBuilder potrebbe essere utilizzato automaticamente. Controlla il tuo ambiente prima di usare + =. Vedi stackoverflow.com/questions/1532461/…
gimel


76

Ho appena guardato il codice che ha fatto questo oggi. Questa è una variazione della risposta di AviewAnew.

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

Il collegamento StringUtils (<- commons.lang 2.x o commons.lang 3.x ) che abbiamo utilizzato proviene da Apache Commons .


... e da dove viene StringUtils?
vwegert

1
Ah, buon punto. È passato un po 'di tempo da quando ho guardato quel codice, ma credo che stessimo usando org.apache.commons.lang.StringUtils.
Salmo Ogre33

Ecco un collegamento in tempo reale al metodo di unione di StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/…
Ryan S

2
Bene grazie. StringUtils # join funziona anche su un Iterable, quindi probabilmente non è necessario convertire prima la tua raccolta in un array.
Roy

47

Il modo in cui scrivo quel ciclo è:

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

Non preoccuparti per le prestazioni di sep. Un incarico è molto veloce. L'hotspot tende comunque a staccare la prima iterazione di un ciclo (poiché spesso ha a che fare con stranezze come i controlli inlining null e mono / bimorfici).

Se lo usi molto (più di una volta), inseriscilo in un metodo condiviso.

C'è un'altra domanda su stackoverflow che tratta di come inserire un elenco di ID in un'istruzione SQL.


42

A partire da Java 8, puoi usare:


3
Questo è buono! Se stai manipolando oggetti che richiedono una conversione di stringa speciale non coperta da toString (), sostituisci Object :: toString con java.util.function.Function <YourType, String> che mappa la tua classe su una String.
Torben

2
Inoltre, puoi usarlo in questo modo: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));per una singola variabile dalla tua raccolta.
numsu

Mi chiedo quale sia la performance di questo stream. Per int [] o long [] o altri array a cui è possibile eseguire semplicemente il cast del valore String, cercherò una soluzione non di streaming. In effetti sto cercando.
Adam

11

Ho trovato l'idioma dell'iteratore elegante, perché ha un test per più elementi (ommited null / empty test per brevità):

public static String convert(List<String> list) {
    String res = "";
    for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
        res += iterator.next() + (iterator.hasNext() ? "," : "");
    }
    return res;
}

... e probabilmente meno efficiente della soluzione accettata, a seconda di quanto sia complessa la chiamata 'hasNext ()'. Inoltre, dovresti probabilmente usare uno StringBuilder invece della concatenazione di stringhe.
Stephen C

OK, se vuoi essere pignolo riguardo all'efficienza, usa uno StringWriter;)
Miguel Ping

8

Ci sono molte soluzioni manuali a questo, ma volevo ribadire e aggiornare la risposta di Julie sopra. Utilizza la classe Joiner di Google Collections .

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

Gestisce var args, iterables e array e gestisce correttamente i separatori di più di un carattere (a differenza della risposta di gimmel). Gestirà anche i valori nulli nella tua lista se ne hai bisogno.


7

Ecco una versione incredibilmente generica che ho creato da una combinazione dei suggerimenti precedenti:

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

7
String.join(", ", collectionOfStrings)

disponibile nell'api Java8.

alternativa a (senza la necessità di aggiungere una dipendenza da google guava):

Joiner.on(",").join(collectionOfStrings);

5

Potresti provare

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

4

Questa sarà la soluzione più breve finora, tranne per l'utilizzo di Guava o Apache Commons

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

Buono con 0,1 en lista di elementi. Ma dovrai controllare l'elenco nullo. Lo uso in GWT, quindi sono bravo senza StringBuilder lì. E per elenchi brevi con solo un paio di elementi va bene anche altrove;)


4

Nel caso in cui qualcuno si sia imbattuto in questo in tempi più recenti, ho aggiunto una semplice variazione usando Java 8 reduce(). Include anche alcune delle soluzioni già citate da altri:

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

  }
}

4

In Android dovresti usare questo:

TextUtils.join(",",collectionOfStrings.toArray());

4

Penso che non sia una buona idea costruire lo sql concatenando i valori della clausola where come stai facendo:

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

Da dove valueXviene un elenco di stringhe.

Primo, se stai confrontando le stringhe devono essere citate, e questo non è banale se le stringhe possono avere una citazione all'interno.

In secondo luogo, se i valori provengono dall'utente o da un altro sistema, è possibile un attacco SQL injection.

È molto più dettagliato, ma quello che dovresti fare è creare una stringa come questa:

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

e quindi associare le variabili con Statement.setString(nParameter,parameterValue).


3

Solo un altro metodo per affrontare questo problema. Non è il più breve, ma è efficiente e porta a termine il lavoro.

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

2

Esistono alcune librerie Java di terze parti che forniscono il metodo di unione delle stringhe, ma probabilmente non vorrai iniziare a utilizzare una libreria solo per qualcosa di semplice come questo. Vorrei solo creare un metodo di supporto come questo, che penso sia un po 'migliore della tua versione, utilizza StringBuffer, che sarà più efficiente se hai bisogno di unire molte stringhe e funziona su una raccolta di qualsiasi 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();
}

Un altro suggerimento con l'utilizzo di Collection.toString () è più breve, ma si basa sul fatto che Collection.toString () restituisce una stringa in un formato molto specifico, su cui personalmente non vorrei fare affidamento.


2

Se usi Spring, puoi fare:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(pacchetto org.springframework.util)


1

Non sono sicuro di quanto sia "sofisticato", ma è certamente un po 'più breve. Funzionerà con diversi tipi di raccolta, ad esempio Set <Integer>, List <String>, ecc.

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

Esercizio per il lettore : modifica questo metodo in modo che gestisca correttamente una raccolta nulla / vuota :)


1

Ciò che rende il codice brutto è la gestione speciale per il primo caso. La maggior parte delle righe in questo piccolo frammento sono dedicate non a svolgere il lavoro di routine del codice, ma a gestire quel caso speciale. E questo è ciò che alternative come la risoluzione di Gimel, spostando la gestione speciale fuori dal ciclo. C'è un caso speciale (beh, potresti vedere sia l'inizio che la fine come casi speciali, ma solo uno di essi deve essere trattato in modo speciale), quindi gestirlo all'interno del ciclo è inutilmente complicato.


1

Ho appena controllato un test per il mio dollaro della biblioteca :

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

crea un wrapper fluente attorno a liste / array / stringhe / ecc usando solo un'importazione statica :$ .

NB :

utilizzando gli intervalli l'elenco precedente può essere riscritto come $(1, 5).join(",")


1

La cosa bella dell'espressione IN è che se hai valori ripetuti, il risultato non cambia. Quindi, duplica semplicemente il primo elemento ed elabora l'intero elenco. Ciò presuppone che ci sia almeno un elemento nell'elenco. Se non ci sono elementi, suggerirei di verificarlo prima e poi di non eseguire affatto l'SQL.

Questo farà il trucco, è ovvio in quello che sta facendo e non si basa su alcuna libreria esterna:

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

1

Anche se penso che la soluzione migliore sia usare Joiner di Guava, se dovessi codificarlo a mano trovo questo approccio più elegante del "primo" flag o del taglio dell'ultima virgola.

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


1

Un'altra opzione, basata su ciò che vedo qui (con lievi modifiche).

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

1

I 'metodi' di join sono disponibili in Arrays e nelle classi che si estendono AbstractCollectionsma non sovrascrivono il toString()metodo (come praticamente tutte le raccolte injava.util ).

Per esempio:

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

Questo è un modo abbastanza strano poiché funziona solo per numeri simili ai dati SQL.


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

StringUtils da springframeowrk: spring-core



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)

1
Questa è una cattiva pratica. Non si può presumere che l'implementazione di toString cambi.
drindt

0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

La rappresentazione di stringa consiste in un elenco degli elementi della raccolta nell'ordine in cui vengono restituiti dal suo iteratore, racchiusi tra parentesi quadre ("[]"). Gli elementi adiacenti sono separati dai caratteri "," (virgola e spazio).

AbstractCollection javadoc


0

List token = new ArrayList (risultato); costruttore StringBuilder finale = nuovo StringBuilder ();

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

builder.toString ();

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.