Die raffinierteste Methode zum Erstellen von durch Kommas getrennten Zeichenfolgen aus einer Sammlung / einem Array / einer Liste?

98

Während meiner Arbeit mit Datenbanken habe ich festgestellt, dass ich Abfragezeichenfolgen schreibe und in diese Zeichenfolgen muss ich die where-Klausel aus einer Liste / einem Array / einer Sammlung mit mehreren Einschränkungen versehen. Sollte so aussehen:

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

Sie können dies vereinfachen, indem Sie dies auf die Frage reduzieren, dass Sie eine Sammlung von Zeichenfolgen haben und eine durch Kommas getrennte Liste dieser Zeichenfolgen in nur einer Zeichenfolge erstellen möchten.

Mein Ansatz, den ich bisher verwendet habe, ist ungefähr so:

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

Aber das ist, wie Sie sehen können, sehr hässlich. Sie können auf den ersten Blick nicht sehen, was dort passiert, insbesondere wenn die erstellten Zeichenfolgen (wie jede SQL-Abfrage) kompliziert werden.

Was ist Ihre (elegantere) Art?

maerch
quelle
Vermutlich sollte die oben gezeigte SQL tatsächlich so aussehen: Wählen Sie * von customer aus, wobei customer.id in (34, 26, 2);
Dónal
Es gibt einen schwierigen Teil, wenn Listenelemente (Zeichenfolgen) selbst Kommas oder doppelte Anführungszeichen enthalten und mit Anführungszeichen versehen werden müssen. Wenn ich nichts verpasst habe, berücksichtigen die obigen Beispiele dies nicht und ich hasse die Idee, alle Texte zu durchlaufen und nach Kommas zu suchen. Gibt es Ihrer Meinung nach einen besseren Weg, dies zu lösen?
Samurai Girl
Überprüfen Sie diese Antwort aus ... stackoverflow.com/a/15815631/728610
Arvind Sridharan
Haben Sie jemals stackoverflow.com/questions/10850753/… überprüft ?
Hiren Patel
Das sollte es tun. stackoverflow.com/a/15815631/3157062
Parag Jadhav

Antworten:

85

Hinweis: Diese Antwort war gut, als sie vor 11 Jahren geschrieben wurde, aber jetzt gibt es weitaus bessere Möglichkeiten, dies sauberer in einer einzigen Zeile zu tun, sowohl mit integrierten Java-Klassen als auch mit einer Dienstprogrammbibliothek. Siehe andere Antworten unten.


Da Zeichenfolgen unveränderlich sind, können Sie die StringBuilder-Klasse verwenden, wenn Sie die Zeichenfolge im Code ändern möchten.

Die StringBuilder-Klasse kann als veränderbares String-Objekt angesehen werden, das mehr Speicher reserviert, wenn sein Inhalt geändert wird.

Der ursprüngliche Vorschlag in der Frage kann noch deutlicher und effizienter geschrieben werden, indem das redundante nachfolgende Komma berücksichtigt wird :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";
gimel
quelle
7
Beachten Sie, dass Ihre Sammlung mindestens ein Element enthalten muss.
Guus
3
Siehe oben gestimmt Antwort - code.google.com/p/guava-libraries/wiki/StringsExplained
gimel
Eine leere Liste finden Sie im vorgeschlagenen Fix.
Gimel
1
Die Antwort der Guave ist besser. Das Rad muss nicht neu erfunden werden.
Davidjnelson
1
@ xtreme-biker Mit einem einigermaßen modernen Compiler kann StringBuilder automatisch verwendet werden. Überprüfen Sie Ihre Umgebung, bevor Sie + = verwenden. Siehe stackoverflow.com/questions/1532461/...
gimel
89

Verwenden Sie die Google Guava API ‚s - joinMethode:

Joiner.on(",").join(collectionOfStrings);
Julie
quelle
4
Heutzutage heißt die Klasse Joiner; google-collections.googlecode.com/svn/trunk/javadoc/com/google/…
Jonik
2
Und heute sind Sammlungen veraltet. Verwenden Sie stattdessen Google Guava .
Darioo
12
In der Zwischenzeit bleibt org.apache.commons.lang.StringUtils unverändert. :-)
Ogre Psalm33
1
Guave hat sich bewegt. siehe github.com/google/guava/wiki/StringsExplained
gimel
76

Ich habe mir gerade den Code angesehen, der dies heute getan hat. Dies ist eine Variation der Antwort von AviewAnew.

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

Die von uns verwendeten StringUtils (<- commons.lang 2.x oder commons.lang 3.x link ) stammen von Apache Commons .

Oger Psalm33
quelle
... und woher kommt StringUtils?
Vwegert
1
Ah, guter Punkt. Es ist schon eine Weile her, seit ich mir diesen Code angesehen habe, aber ich glaube, wir haben org.apache.commons.lang.StringUtils verwendet.
Oger Psalm33
Hier ist ein Live-Link zur Join-Methode von StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/…
Ryan S
2
Nett, danke. StringUtils # join funktioniert auch mit einer Iterable, sodass Sie Ihre Sammlung wahrscheinlich nicht zuerst in ein Array konvertieren müssen.
Roy
47

Die Art und Weise, wie ich diese Schleife schreibe, ist:

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

Mach dir keine Sorgen über die Leistung von sep. Eine Aufgabe ist sehr schnell. Hotspot neigt ohnehin dazu, die erste Iteration einer Schleife abzuziehen (da es häufig mit Kuriositäten wie Null- und mono- / bimorphen Inlining-Prüfungen zu tun hat).

Wenn Sie es häufig (mehrmals) verwenden, legen Sie es in einer gemeinsamen Methode ab.

Es gibt eine weitere Frage zum Stackoverflow, die sich mit dem Einfügen einer Liste von IDs in eine SQL-Anweisung befasst.

Tom Hawtin - Tackline
quelle
41

Seit Java 8 können Sie Folgendes verwenden:

Abdull
quelle
3
Das ist gut! Wenn Sie Objekte bearbeiten, für die eine spezielle Zeichenfolgenkonvertierung erforderlich ist, die nicht von toString () abgedeckt wird, ersetzen Sie Object :: toString durch eine java.util.function.Function <YourType, String>, die Ihre Klasse einem String zuordnet.
Torben
2
Sie können es auch folgendermaßen verwenden: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));für eine einzelne Variable aus Ihrer Sammlung.
Numsu
Ich wundere mich über die Leistung davon stream. Für int [] oder long [] oder andere Arrays, in die der Wert einfach umgewandelt werden kann String, würde ich nach einer Nicht-Streaming-Lösung suchen. Tatsächlich suche ich.
Adam
11

Ich fand das Iterator-Idiom elegant, weil es einen Test für mehr Elemente enthält (aus Gründen der Kürze wurde der Null- / Leer-Test nicht angegeben):

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
quelle
... und wahrscheinlich weniger effizient als die akzeptierte Lösung, je nachdem, wie komplex der Aufruf von 'hasNext ()' ist. Außerdem sollten Sie wahrscheinlich einen StringBuilder anstelle einer String-Verkettung verwenden.
Stephen C
OK, wenn Sie in Bezug auf Effizienz wählerisch sein möchten, verwenden Sie einen StringWriter;)
Miguel Ping
8

Es gibt viele manuelle Lösungen dafür, aber ich wollte Julies Antwort oben wiederholen und aktualisieren. Verwenden Sie die Joiner-Klasse für Google-Sammlungen .

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

Es verarbeitet var args, iterables und arrays und behandelt Trennzeichen von mehr als einem Zeichen ordnungsgemäß (im Gegensatz zu gimmels Antwort). Bei Bedarf werden auch Nullwerte in Ihrer Liste verarbeitet.

Fall Nelson
quelle
7

Hier ist eine unglaublich generische Version, die ich aus einer Kombination der vorherigen Vorschläge erstellt habe:

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
quelle
7
String.join(", ", collectionOfStrings)

verfügbar in der Java8-API.

Alternative zu (ohne dass eine Google Guava-Abhängigkeit hinzugefügt werden muss):

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

Du könntest es versuchen

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
quelle
4

Dies ist die bisher kürzeste Lösung, mit Ausnahme der Verwendung von Guava oder Apache Commons

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

Gut mit 0,1 und n Elementliste. Sie müssen jedoch nach einer Nullliste suchen. Ich benutze dies in GWT, also bin ich dort gut ohne StringBuilder. Und für kurze Listen mit nur ein paar Elementen ist es auch anderswo in Ordnung;)

ich nehme
quelle
4

Für den Fall, dass jemand in jüngerer Zeit darüber gestolpert ist, habe ich eine einfache Variante mit Java 8 hinzugefügt reduce(). Es enthält auch einige der bereits erwähnten Lösungen anderer:

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
quelle
4

In Android sollten Sie Folgendes verwenden:

TextUtils.join(",",collectionOfStrings.toArray());
Pascalius
quelle
4

Ich denke, es ist keine gute Idee, das SQL so zu konstruieren, dass es die Werte der where-Klausel verkettet, wie Sie es tun:

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

Woher valueXkommt eine Liste von Strings.

Erstens, wenn Sie Strings vergleichen, müssen sie in Anführungszeichen gesetzt werden. Dies ist nicht trivial, wenn die Strings ein Zitat enthalten könnten.

Zweitens ist ein SQL-Injection-Angriff möglich, wenn die Werte vom Benutzer oder einem anderen System stammen.

Es ist viel ausführlicher, aber Sie sollten einen String wie den folgenden erstellen:

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

und binden Sie dann die Variablen mit Statement.setString(nParameter,parameterValue).

Telcontar
quelle
3

Nur eine andere Methode, um dieses Problem zu lösen. Nicht die kürzeste, aber es ist effizient und erledigt die Arbeit.

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

Es gibt einige Java-Bibliotheken von Drittanbietern, die eine String-Join-Methode bereitstellen, aber Sie möchten eine Bibliothek wahrscheinlich nicht nur für so etwas Einfaches verwenden. Ich würde nur eine Hilfsmethode wie diese erstellen, die meiner Meinung nach etwas besser ist als Ihre Version. Sie verwendet StringBuffer, der effizienter ist, wenn Sie viele Zeichenfolgen verbinden müssen, und funktioniert für eine Sammlung eines beliebigen Typs.

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

Ein weiterer Vorschlag zur Verwendung von Collection.toString () ist kürzer. Dies setzt jedoch voraus, dass Collection.toString () eine Zeichenfolge in einem ganz bestimmten Format zurückgibt, auf das ich mich persönlich nicht verlassen möchte.

Denis Fradlin
quelle
2

Wenn Sie Spring verwenden, können Sie Folgendes tun:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(Paket org.springframework.util)

Wochen
quelle
1

Ich bin mir nicht sicher, wie "raffiniert" das ist, aber es ist sicherlich etwas kürzer. Es funktioniert mit verschiedenen Arten von Sammlungen, z. B. Set <Integer>, List <String> usw.

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

Übung für den Leser : Ändern Sie diese Methode so, dass sie eine null / leere Sammlung korrekt behandelt :)

Dónal
quelle
1

Was den Code hässlich macht, ist die spezielle Behandlung für den ersten Fall. Die meisten Zeilen in diesem kleinen Ausschnitt widmen sich nicht der Routinearbeit des Codes, sondern der Behandlung dieses Sonderfalls. Und genau das lösen Alternativen wie die von Gimel, indem sie das spezielle Handling außerhalb der Schleife bewegen. Es gibt einen Sonderfall (nun, Sie könnten sowohl Start als auch Ende als Sonderfälle betrachten - aber nur einer von ihnen muss speziell behandelt werden), sodass die Behandlung innerhalb der Schleife unnötig kompliziert ist.

Carl Manaster
quelle
1

Ich habe gerade eingecheckt in einem Test für meine Bibliothek Dollar :

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

Mit nur einem statischen Import wird ein fließender Wrapper um Listen / Arrays / Strings / etc erstellt : $.

NB :

Unter Verwendung von Bereichen kann die vorherige Liste als neu geschrieben werden $(1, 5).join(",")

dfa
quelle
1

Das Schöne am IN-Ausdruck ist, dass sich das Ergebnis nicht ändert, wenn Sie wiederholte Werte haben. Duplizieren Sie einfach das erste Element und verarbeiten Sie die gesamte Liste. Dies setzt voraus, dass mindestens ein Element in der Liste vorhanden ist. Wenn es keine Elemente gibt, würde ich vorschlagen, zuerst danach zu suchen und dann die SQL überhaupt nicht auszuführen.

Dies wird den Trick machen, ist offensichtlich in dem, was es tut und verlässt sich nicht auf externe Bibliotheken:

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

Während ich denke, dass Ihre beste Wette darin besteht, Joiner von Guava zu verwenden, finde ich diesen Ansatz eleganter, wenn ich ihn von Hand codiere, als die 'erste' Flagge oder das letzte Komma abzuschneiden.

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

Wenn Sie ein Array haben, können Sie Folgendes tun:

Arrays.asList(parameters).toString()
wolkiges Wetter
quelle
1

Eine weitere Option, basierend auf dem, was ich hier sehe (mit geringfügigen Änderungen).

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
quelle
1

Join-Methoden sind in Arrays und den Klassen verfügbar, die AbstractCollectionsdie toString()Methode erweitern , aber nicht überschreiben (wie praktisch alle Sammlungen in java.util).

Zum Beispiel:

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

Das ist ziemlich seltsam, da es nur für Zahlen funktioniert, die SQL-ähnlich sind.

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

StringUtils von springframeowrk: Federkern

Sridhar
quelle
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)
Julius Caesar
quelle
1
Dies ist eine schlechte Praxis. Sie können nicht davon ausgehen, dass sich die toString-Implementierung ändert.
Drindt
0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

Die Zeichenfolgendarstellung besteht aus einer Liste der Elemente der Sammlung in der Reihenfolge, in der sie vom Iterator zurückgegeben werden, in eckigen Klammern ("[]"). Benachbarte Elemente werden durch die Zeichen "," (Komma und Leerzeichen) getrennt.

AbstractCollection javadoc

Todd Gatts
quelle
0

List token = new ArrayList (Ergebnis); final StringBuilder builder = new 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 ();

UPS
quelle