Le moyen le plus sophistiqué de créer des chaînes séparées par des virgules à partir d'une collection / tableau / liste?

98

Au cours de mon travail avec les bases de données, j'ai remarqué que j'écrivais des chaînes de requête et que dans ces chaînes, je dois mettre plusieurs restrictions dans la clause where d'une liste / tableau / collection. Devrait ressembler à ceci:

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

Vous pouvez simplifier cela en réduisant cela à la question que vous avez une collection de chaînes et que vous souhaitez créer une liste séparée par des virgules de ces chaînes dans une seule chaîne.

Mon approche que j'ai utilisée jusqu'à présent est quelque chose comme ça:

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

Mais c'est comme vous pouvez le voir très moche. Vous ne pouvez pas voir ce qui s'y passe au premier regard, en particulier lorsque les chaînes construites (comme chaque requête SQL) se compliquent.

Quelle est votre (plus) élégante manière?

maerch
la source
Vraisemblablement, le SQL montré ci-dessus devrait ressembler à ceci: select * from customer where customer.id in (34, 26, 2);
Dónal
Il y a une partie délicate, lorsque les éléments de liste (chaînes) eux-mêmes contiennent des virgules ou des guillemets doubles et qu'ils doivent être échappés avec des guillemets. Si je n'ai rien manqué, les exemples ci-dessus ne le considèrent pas et je déteste l'idée de parcourir tous les textes et de rechercher des virgules. Pensez-vous qu'il existe une meilleure façon de résoudre ce problème?
Samurai Girl
Vérifiez cette réponse ... stackoverflow.com/a/15815631/728610
Arvind Sridharan
Avez-vous déjà vérifié stackoverflow.com/questions/10850753/... ?
Hiren Patel
Cela devrait le faire. stackoverflow.com/a/15815631/3157062
Parag Jadhav

Réponses:

85

Remarque: cette réponse était bonne lorsqu'elle a été écrite il y a 11 ans, mais il existe maintenant de bien meilleures options pour le faire plus proprement sur une seule ligne, à la fois en utilisant uniquement les classes intégrées Java ou en utilisant une bibliothèque d'utilitaires. Voir les autres réponses ci-dessous.


Étant donné que les chaînes sont immuables, vous pouvez utiliser la classe StringBuilder si vous allez modifier la chaîne dans le code.

La classe StringBuilder peut être considérée comme un objet String mutable qui alloue plus de mémoire lorsque son contenu est modifié.

La suggestion originale dans la question peut être écrite encore plus clairement et plus efficacement, en prenant soin de la virgule de fin redondante :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";
gimel
la source
7
Notez que cela nécessite que votre collection contienne au moins un élément.
Guus
3
Voir la réponse la plus votée - code.google.com/p/guava-libraries/wiki/StringsExplained
gimel
Voir le correctif suggéré pour une liste vide.
gimel le
1
la réponse de la goyave est meilleure. pas besoin de réinventer la roue.
davidjnelson
1
@ xtreme-biker Avec un compilateur raisonnablement moderne, StringBuilder peut être utilisé automatiquement. Vérifiez votre environnement avant d'utiliser + =. Voir stackoverflow.com/questions/1532461/…
gimel
89

Utilisez l' API Google goyave de » joinméthode:

Joiner.on(",").join(collectionOfStrings);
Julie
la source
4
Aujourd'hui, la classe est appelée Joiner; google-collections.googlecode.com/svn/trunk/javadoc/com/google/…
Jonik
2
Et aujourd'hui, Collections est obsolète. Utilisez plutôt Google Guava .
darioo
12
Pendant ce temps, org.apache.commons.lang.StringUtils reste inchangé. :-)
Ogre Psalm33
1
la goyave a déménagé. voir github.com/google/guava/wiki/StringsExplained
gimel
76

Je viens de regarder le code qui a fait cela aujourd'hui. Ceci est une variante de la réponse d'AviewAnew.

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

Le StringUtils (<- commons.lang 2.x ou lien commons.lang 3.x ) que nous avons utilisé provient d' Apache Commons .

Ogre Psaume33
la source
... et d'où vient StringUtils?
vwegert
1
Ah, bon point. Cela fait un moment que j'ai regardé ce code, mais je pense que nous utilisions org.apache.commons.lang.StringUtils.
Ogre Psalm33
Voici un lien direct à la méthode join StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/...~~V~~aux~~singular~~3rd
Ryan S
2
Bien, merci. StringUtils # join fonctionne également sur un Iterable, il n'est donc probablement pas nécessaire de convertir d'abord votre collection en tableau.
Roy
47

La façon dont j'écris cette boucle est:

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

Ne vous inquiétez pas des performances de sep. Une mission est très rapide. Le point d'accès a tendance à décoller de toute façon la première itération d'une boucle (car il doit souvent faire face à des bizarreries telles que les vérifications d'inlining nulles et mono / bimorphes).

Si vous l'utilisez beaucoup (plus d'une fois), placez-le dans une méthode partagée.

Il y a une autre question sur stackoverflow traitant de la façon d'insérer une liste d'identifiants dans une instruction SQL.

Tom Hawtin - Tacle
la source
42

Depuis Java 8, vous pouvez utiliser:

Abdull
la source
3
C'est bon! Si vous manipulez des objets qui nécessitent une conversion de chaîne spéciale non couverte par toString (), remplacez Object :: toString par un java.util.function.Function <YourType, String> qui mappe votre classe à une chaîne.
Torben
2
Vous pouvez également l'utiliser comme ceci: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));pour une seule variable de votre collection.
numsu
Je m'interroge sur la performance de cela stream. Pour les tableaux int [] ou long [] ou d'autres où la valeur peut simplement être castée String, je chercherais une solution sans streaming. En fait je cherche.
Adam
11

J'ai trouvé l'idiome de l'itérateur élégant, car il a un test pour plus d'éléments (test nul / vide omis pour la brièveté):

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
la source
... et probablement moins efficace que la solution acceptée, selon la complexité de l'appel 'hasNext ()'. En outre, vous devriez probablement utiliser un StringBuilder plutôt qu'une concaténation String.
Stephen C
OK, si vous voulez être pointilleux sur l'efficacité, utilisez un StringWriter;)
Miguel Ping
8

Il y a beaucoup de solutions manuelles à cela, mais je voulais réitérer et mettre à jour la réponse de Julie ci-dessus. Utilisez la classe Joiner de google collections .

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

Il gère les var args, les itérables et les tableaux et gère correctement les séparateurs de plus d'un caractère (contrairement à la réponse de gimmel). Il gérera également les valeurs nulles de votre liste si vous en avez besoin.

cas nelson
la source
7

Voici une version incroyablement générique que j'ai construite à partir d'une combinaison des suggestions précédentes:

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

disponible dans l'API Java8.

alternative à (sans avoir besoin d'ajouter une dépendance google guava):

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

Tu pourrais essayer

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
la source
4

Ce sera la solution la plus courte à ce jour, sauf en utilisant Guava ou Apache Commons

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

Bon avec une liste d'éléments 0,1 et n. Mais vous devrez vérifier la liste nulle. Je l'utilise dans GWT, donc je suis bon sans StringBuilder là-bas. Et pour les listes courtes avec juste quelques éléments, ça va aussi ailleurs;)

Je prends
la source
4

Au cas où quelqu'un trébucherait dessus plus récemment, j'ai ajouté une simple variation utilisant Java 8 reduce(). Il comprend également certaines des solutions déjà mentionnées par d'autres:

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
la source
4

Dans Android, vous devez utiliser ceci:

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

Je pense que ce n'est pas une bonne idée de construire le SQL concaténant les valeurs de la clause where comme vous le faites:

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

D'où valueXvient une liste de chaînes.

Premièrement, si vous comparez des chaînes, elles doivent être entre guillemets, et ce n'est pas anodin si les chaînes peuvent avoir un guillemet à l'intérieur.

Deuxièmement, si les valeurs proviennent de l'utilisateur ou d'un autre système, une attaque par injection SQL est possible.

C'est beaucoup plus détaillé, mais ce que vous devriez faire est de créer une chaîne comme celle-ci:

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

puis liez les variables avec Statement.setString(nParameter,parameterValue).

Telcontar
la source
3

Juste une autre méthode pour résoudre ce problème. Pas le plus court, mais il est efficace et fait le travail.

/**
 * 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
la source
2

Il existe des bibliothèques Java tierces qui fournissent une méthode de jointure de chaîne, mais vous ne voulez probablement pas commencer à utiliser une bibliothèque juste pour quelque chose de simple comme ça. Je voudrais simplement créer une méthode d'aide comme celle-ci, qui je pense est un peu meilleure que votre version, elle utilise StringBuffer, qui sera plus efficace si vous avez besoin de joindre plusieurs chaînes, et cela fonctionne sur une collection de n'importe quel type.

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

Une autre suggestion avec l'utilisation de Collection.toString () est plus courte, mais cela repose sur Collection.toString () renvoyant une chaîne dans un format très spécifique, sur lequel je ne voudrais personnellement pas me fier.

Denis Fradlin
la source
2

Si vous utilisez Spring, vous pouvez faire:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(package org.springframework.util)

weekens
la source
1

Je ne sais pas à quel point c'est "sophistiqué", mais c'est certainement un peu plus court. Il fonctionnera avec différents types de collections, par exemple 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) + ")";
}

Exercice pour le lecteur : modifiez cette méthode pour qu'elle gère correctement une collection nulle / vide :)

Dónal
la source
1

Ce qui rend le code moche, c'est la manipulation spéciale pour le premier cas. La plupart des lignes de ce petit extrait de code sont consacrées, non pas au travail de routine du code, mais à la gestion de ce cas particulier. Et c'est ce que résolvent des alternatives comme celle de gimel, en déplaçant la manipulation spéciale en dehors de la boucle. Il y a un cas particulier (enfin, vous pouvez voir à la fois le début et la fin comme des cas spéciaux - mais un seul d'entre eux doit être traité spécialement), donc le gérer à l'intérieur de la boucle est inutilement compliqué.

Carl Manaster
la source
1

Je viens de faire un test pour mon dollar de bibliothèque :

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

il créer un wrapper autour des listes couramment / tableaux / chaînes / etc en utilisant une seule importation statique : $.

NB :

en utilisant des plages, la liste précédente peut être réécrite comme $(1, 5).join(",")

dfa
la source
1

L'avantage de l'expression IN est que si vous avez des valeurs répétées, cela ne change pas le résultat. Alors, dupliquez simplement le premier élément et traitez la liste entière. Cela suppose qu'il y a au moins un élément dans la liste. S'il n'y a pas d'éléments, je suggérerais de vérifier cela d'abord, puis de ne pas exécuter du tout le SQL.

Cela fera l'affaire, est évident dans ce qu'il fait et ne repose sur aucune bibliothèque externe:

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

Bien que je pense que votre meilleur pari est d'utiliser Joiner de Guava, si je devais le coder à la main, je trouve cette approche plus élégante que le «premier» drapeau ou couper la dernière virgule.

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();
}
Victor
la source
1

si vous avez un tableau, vous pouvez faire:

Arrays.asList(parameters).toString()
temps nuageux
la source
1

Une autre option, basée sur ce que je vois ici (avec de légères modifications).

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
la source
1

Les 'méthodes' de jointure sont disponibles dans les tableaux et les classes qui s'étendent AbstractCollectionsmais ne remplacent pas la toString()méthode (comme pratiquement toutes les collections dans java.util).

Par exemple:

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

C'est une manière assez étrange car cela ne fonctionne que pour les nombres de même que les données SQL.

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

StringUtils de springframeowrk: spring-core

Sridhar
la source
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)
Jules César
la source
1
C'est une mauvaise pratique. Vous ne pouvez pas supposer que l'implémentation de toString change.
drindt
0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

La représentation sous forme de chaîne consiste en une liste des éléments de la collection dans l'ordre dans lequel ils sont renvoyés par son itérateur, entre crochets ("[]"). Les éléments adjacents sont séparés par les caractères "," (virgule et espace).

RésuméCollection javadoc

Todd Gatts
la source
0

Jeton de liste = new ArrayList (résultat); constructeur final StringBuilder = nouveau 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
la source