Le moyen le plus simple de délimiter une liste par des virgules?


104

Quelle est la manière la plus claire de délimiter une liste par des virgules en Java?

Je connais plusieurs façons de le faire, mais je me demande quelle est la meilleure façon (où «meilleur» signifie le plus clair et / ou le plus court, pas le plus efficace.

J'ai une liste et je veux faire une boucle dessus, en imprimant chaque valeur. Je veux imprimer une virgule entre chaque élément, mais pas après le dernier (ni avant le premier).

List --> Item ( , Item ) *
List --> ( Item , ) * Item

Exemple de solution 1:

boolean isFirst = true;
for (Item i : list) {
  if (isFirst) {
    System.out.print(i);        // no comma
    isFirst = false;
  } else {
    System.out.print(", "+i);   // comma
  }
}

Exemple de solution 2 - créer une sous-liste:

if (list.size()>0) {
  System.out.print(list.get(0));   // no comma
  List theRest = list.subList(1, list.size());
  for (Item i : theRest) {
    System.out.print(", "+i);   // comma
  }
}

Exemple de solution 3:

  Iterator<Item> i = list.iterator();
  if (i.hasNext()) {
    System.out.print(i.next());
    while (i.hasNext())
      System.out.print(", "+i.next());
  }

Ceux-ci traitent le premier élément spécialement; on pourrait plutôt traiter le dernier spécialement.

Incidemment, voici comment List toString est implémenté (il est hérité de AbstractCollection), dans Java 1.6:

public String toString() {
    Iterator<E> i = iterator();
    if (! i.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = i.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! i.hasNext())
            return sb.append(']').toString();
        sb.append(", ");
    }
}

Il quitte la boucle tôt pour éviter la virgule après le dernier élément. BTW: c'est la première fois que je me souviens avoir vu "(this Collection)"; voici le code pour le provoquer:

List l = new LinkedList();
l.add(l);
System.out.println(l);

J'accueille toutes les solutions, même si elles utilisent des bibliothèques inattendues (regexp?); et aussi des solutions dans des langages autres que Java (par exemple, je pense que Python / Ruby ont une fonction intercalaire - comment est- ce implémenté?).

Clarification : par bibliothèques, j'entends les bibliothèques Java standard. Pour les autres bibliothèques, je les considère avec d'autres langages et je suis intéressé de savoir comment ils sont implémentés.

EDIT toolkit a mentionné une question similaire: dernière itération de la boucle for améliorée en java

Et un autre: le dernier élément d'une boucle mérite-t-il un traitement séparé?


Réponses:


226

Java 8 et versions ultérieures

Utilisation de la StringJoinerclasse:

StringJoiner joiner = new StringJoiner(",");
for (Item item : list) {
    joiner.add(item.toString());
}
return joiner.toString();

Utilisation Stream, et Collectors:

return list.stream().
       map(Object::toString).
       collect(Collectors.joining(",")).toString();

Java 7 et versions antérieures

Voir aussi # 285523

String delim = "";
for (Item i : list) {
    sb.append(delim).append(i);
    delim = ",";
}

1
Une façon très surprenante de stocker l'état! C'est presque polymorphe OO. Je n'aime pas l'affectation inutile à chaque boucle, mais je parie que c'est plus efficace qu'un if. La plupart des solutions répètent un test que nous savons ne pas être vrai - bien qu'inefficace, elles sont probablement les plus claires. Merci pour le lien!
13ren

1
J'aime ça, mais parce que c'est intelligent et surprenant, j'ai pensé qu'il ne serait pas approprié de l'utiliser (surprendre n'est pas bon!). Cependant, je ne pouvais pas m'en empêcher. Je n'en suis toujours pas sûr; cependant, il est si court qu'il est facile de travailler à condition qu'il y ait un commentaire pour vous avertir.
13ren

6
@ 13ren: Vous et l'oncle Bob devez avoir une discussion sur le code qui soit "intelligente et surprenante".
Stefan Kendall

J'ai l'impression d'avoir perdu des années sans le savoir ^^;
Lac

Pourquoi Java a-t-il besoin de transformer une telle tâche en un code laid?
Eduardo

82
org.apache.commons.lang3.StringUtils.join(list,",");

1
Trouvé la source: svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org / ... La méthode join a 9 surcharges. Ceux basés sur des itérations sont comme "Exemple de solution 3"; ceux basés sur un index utilisent: if (i> startIndex) {<add separator>}
13ren

Je suis vraiment après les implémentations. C'est une bonne idée de l'envelopper dans une fonction, mais en pratique, mon délimiteur est parfois une nouvelle ligne, et chaque ligne est également indentée à une profondeur spécifiée. À moins que ... joindre (liste, "\ n" + retrait) ... cela fonctionnera-t-il toujours? Désolé, je pense juste à voix haute.
13ren

2
À mon avis, c'est la solution la plus jolie et la plus courte
Jesper Rønn-Jensen

probablement, list.iterator ()?
Vanuan

32

Java 8 propose plusieurs nouvelles façons de procéder:

Exemple:

// Util method for strings and other char sequences
List<String> strs = Arrays.asList("1", "2", "3");
String listStr1 = String.join(",", strs);

// For any type using streams and collectors
List<Object> objs = Arrays.asList(1, 2, 3);
String listStr2 = objs.stream()
    .map(Object::toString)
    .collect(joining(",", "[", "]"));

// Using the new StringJoiner class
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.setEmptyValue("<empty>");
for (Integer i : objs) {
  joiner.add(i.toString());
}
String listStr3 = joiner.toString();

L'approche utilisant les flux suppose import static java.util.stream.Collectors.joining;.


Ceci est à mon humble avis, si vous utilisez java 8, la meilleure réponse.
scravy


7

Si vous utilisez Spring Framework, vous pouvez le faire avec StringUtils :

public static String arrayToDelimitedString(Object[] arr)
public static String arrayToDelimitedString(Object[] arr, String delim)
public static String collectionToCommaDelimitedString(Collection coll)
public static String collectionToCommaDelimitedString(Collection coll, String delim)

Merci, mais comment est-il mis en œuvre?
13ren

6

Basé sur l'implémentation de List toString de Java:

Iterator i = list.iterator();
for (;;) {
  sb.append(i.next());
  if (! i.hasNext()) break;
  ab.append(", ");
}

Il utilise une grammaire comme celle-ci:

List --> (Item , )* Item

En étant basé sur le dernier au lieu de basé sur le premier, il peut vérifier les sauts de virgule avec le même test pour vérifier la fin de la liste. Je pense que celui-ci est très élégant, mais je ne suis pas sûr de la clarté.


4
String delimiter = ",";
StringBuilder sb = new StringBuilder();
for (Item i : list) {
    sb.append(delimiter).append(i);
}
sb.toString().replaceFirst(delimiter, "");

4

Il existe un joli moyen d'y parvenir en utilisant Java 8:

List<String> list = Arrays.asList(array);
String joinedString = String.join(",", list);

TextUtils.join (",", liste);
Arul Pandian

3
for(int i=0, length=list.size(); i<length; i++)
  result+=(i==0?"":", ") + list.get(i);

Oui, c'est un peu cryptique, mais une alternative à ce qui a déjà été publié.
cherouvim

1
Je pense que le code a l'air bien, à mon humble avis, c'est plus joli que votre autre réponse et c'est à peine «cryptique». Enveloppez-le dans une méthode appelée Join et votre rire, mais je m'attendrais à ce que la langue ait une meilleure façon de résoudre ce qui est vraiment un problème commun.
tarn

@tarn: vous pensez que ça a l'air bien parce que vous avez un fond Python / perl;) Je l'aime aussi
cherouvim

3

Une option pour la boucle foreach est:

StringBuilder sb = new StringBuilder();
for(String s:list){
  if (sb.length()>0) sb.append(",");
  sb.append(s);
}

2
StringBuffer result = new StringBuffer();
for(Iterator it=list.iterator; it.hasNext(); ) {
  if (result.length()>0)
    result.append(", ");
  result.append(it.next());
}

Mise à jour : comme Dave Webb l'a mentionné dans les commentaires, cela peut ne pas produire de résultats corrects si les premiers éléments de la liste sont des chaînes vides.


meh .. a toujours le if dans la boucle.
sfossen

Vous pouvez utiliser une boucle foreach pour la raccourcir.
Peter Lawrey

Cela ne fonctionnera pas si le premier élément de la liste est une chaîne vide.
Dave Webb

@ Dave Webb: Oui, merci. La question n'a cependant pas une telle exigence.
cherouvim

@cherovium: La question ne dit pas qu'aucun des éléments ne sera la chaîne vide, c'est donc une exigence implicite. Si vous créez un fichier CSV, les champs vides peuvent être assez courants.
Dave Webb

2

Je fais généralement ceci:

StringBuffer sb = new StringBuffer();
Iterator it = myList.iterator();
if (it.hasNext()) { sb.append(it.next().toString()); }
while (it.hasNext()) { sb.append(",").append(it.next().toString()); }

Bien que je pense que je vais procéder à une vérification à partir de maintenant selon l'implémentation Java;)


C'est la même logique que l'exemple 3, je l'aime bien car il ne fait rien d'inutile. Cependant, je ne sais pas si c'est le plus clair. BTW: c'est légèrement plus efficace si vous mettez le while dans le if, en évitant deux tests lorsque la liste est vide. Cela rend aussi les choses un peu plus claires, pour moi.
13ren

Pour l'amour du ciel, utilisez un StringBuilder
scravy

2

Si vous pouvez utiliser Groovy (qui s'exécute sur la JVM):

def list = ['a', 'b', 'c', 'd']
println list.join(',')

merci, mais je suis intéressé par les implémentations. Comment celui-ci est-il mis en œuvre?
13ren

2

(Copiez-collez ma propre réponse à partir d' ici .) La plupart des solutions décrites ici sont un peu exagérées, à mon humble avis, en particulier celles qui reposent sur des bibliothèques externes. Il y a une belle expression propre et claire pour obtenir une liste séparée par des virgules que j'ai toujours utilisée. Il repose sur l'opérateur conditionnel (?):

Edit : Solution d'origine correcte, mais non optimale selon les commentaires. Essayer une deuxième fois:

int[] array = {1, 2, 3};
StringBuilder builder = new StringBuilder();
for (int i = 0 ;  i < array.length; i++)
       builder.append(i == 0 ? "" : ",").append(array[i]);

Voilà, en 4 lignes de code comprenant la déclaration du tableau et le StringBuilder.

2e édition : si vous avez affaire à un itérateur:

    List<Integer> list = Arrays.asList(1, 2, 3);
    StringBuilder builder = new StringBuilder();
    for (Iterator it = list.iterator(); it.hasNext();)
        builder.append(it.next()).append(it.hasNext() ? "," : "");

Ceci et stackoverflow.com/questions/668952/… ci-dessus sont similaires. C'est court et clair, mais si vous n'avez qu'un itérateur, vous devez d'abord convertir. Inefficace, mais cela en vaut peut-être la peine pour la clarté de cette approche?
13ren

Ugh, concaténation de chaînes créant un StringBuilder temporaire dans l'utilisation d'un StringBuilder. Inefficace. Mieux (plus de Java, mais meilleur code en cours d'exécution) serait "if (i> 0) {builder.append (',');}" suivi de builder.append (array [i]);
Eddie

Oui, si vous regardez dans le bytecode, vous avez raison. Je ne peux pas croire qu'une question aussi simple puisse devenir si compliquée. Je me demande si le compilateur peut optimiser ici.
Julien Chastang

Fourni 2ème solution, espérons-le mieux.
Julien Chastang

Il serait peut-être préférable de les diviser (pour que les gens puissent voter sur l'un ou l'autre).
13ren

1

J'utilise généralement quelque chose de similaire à la version 3. Cela fonctionne bien c / c ++ / bash / ...: P


1

Je ne l'ai pas compilé ... mais devrait fonctionner (ou être sur le point de travailler).

public static <T> String toString(final List<T> list, 
                                  final String delim)
{
    final StringBuilder builder;

    builder = new StringBuilder();

    for(final T item : list)
    {
        builder.append(item);
        builder.append(delim);
    }

    // kill the last delim
    builder.setLength(builder.length() - delim.length());

    return (builder.toString());
}

J'aime cette façon, je préférerais juste un autre formatage. Mais pour mentionner un petit correctif: si la liste est vide, cela lèvera une exception IndexOutOfBounds. Donc, faites une vérification avant setLength, ou utilisez Math.max (0, ...)
tb-

1

C'est très court, très clair, mais donne à ma sensibilité des horreurs rampantes. Il est également un peu difficile de s'adapter à différents délimiteurs, en particulier s'il s'agit d'une chaîne (et non d'un caractère).

for (Item i : list)
  sb.append(',').append(i);
if (sb.charAt(0)==',') sb.deleteCharAt(0);

Inspiré par: Dernière itération de la boucle for améliorée en java


1
StringBuffer sb = new StringBuffer();

for (int i = 0; i < myList.size(); i++)
{ 
    if (i > 0) 
    {
        sb.append(", ");
    }

    sb.append(myList.get(i)); 
}

Je trouve celui-ci très facile à intégrer dans une classe d'utilité statique, tout en étant très facile à lire et à comprendre également. Il ne nécessite pas non plus de variables ou d'état supplémentaires autres que la liste elle-même, la boucle et le délimiteur.
Joachim H. Skeie

Cela fournira quelque chose comme:Item1Item2, Item3, Item4,

Pour l'amour du ciel, utilisez un StringBuilder
scravy

1

J'aime un peu cette approche, que j'ai trouvée sur un blog il y a quelque temps. Malheureusement, je ne me souviens pas du nom / de l'URL du blog.

Vous pouvez créer une classe utilitaire / d'assistance qui ressemble à ceci:

private class Delimiter
{
    private final String delimiter;
    private boolean first = true;

    public Delimiter(String delimiter)
    {
        this.delimiter = delimiter;
    }

    @Override
    public String toString()
    {
        if (first) {
            first = false;
            return "";
        }

        return delimiter;
    }
}

L'utilisation de la classe helper est simple comme ceci:

StringBuilder sb = new StringBuilder();
Delimiter delimiter = new Delimiter(", ");

for (String item : list) {
    sb.append(delimiter);
    sb.append(item);
}

Juste une note: il devrait être "délimiteur", pas "délimiteur".
Michael Myers

1

Étant donné que votre délimiteur est "," vous pouvez utiliser l'un des éléments suivants:

public class StringDelim {

    public static void removeBrackets(String string) {
        System.out.println(string.substring(1, string.length() - 1));
    }

    public static void main(String... args) {
        // Array.toString() example
        String [] arr = {"Hi" , "My", "name", "is", "br3nt"};
        String string = Arrays.toString(arr);
        removeBrackets(string);

        // List#toString() example
        List<String> list = new ArrayList<String>();
        list.add("Hi");
        list.add("My");
        list.add("name");
        list.add("is");
        list.add("br3nt");
        string = list.toString();
        removeBrackets(string);

        // Map#values().toString() example
        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("1", "Hi");
        map.put("2", "My");
        map.put("3", "name");
        map.put("4", "is");
        map.put("5", "br3nt");
        System.out.println(map.values().toString());
        removeBrackets(string);

        // Enum#toString() example
        EnumSet<Days> set = EnumSet.allOf(Days.class);
        string = set.toString();
        removeBrackets(string);
    }

    public enum Days {
        MON("Monday"),
        TUE("Tuesday"),
        WED("Wednesday"),
        THU("Thursday"),
        FRI("Friday"),
        SAT("Saturday"),
        SUN("Sunday");

        private final String day;

        Days(String day) {this.day = day;}
        public String toString() {return this.day;}
    }
}

Si votre délimiteur est autre chose, cela ne fonctionnera pas pour vous.


1

J'aime cette solution:

String delim = " - ", string = "";

for (String item : myCollection)
    string += delim + item;

string = string.substring(delim.length());

Je suppose qu'il peut également utiliser StringBuilder.


1

À mon avis, c'est le plus simple à lire et à comprendre:

StringBuilder sb = new StringBuilder();
for(String string : strings) {
    sb.append(string).append(',');
}
sb.setLength(sb.length() - 1);
String result = sb.toString();

0

En Python, c'est facile

",". rejoindre (votre liste)

En C #, il existe une méthode statique sur la classe String

String.Join (",", votre liste de chaînes)

Désolé, je ne suis pas sûr de Java, mais je pensais que je ferais un commentaire sur les autres langues. Je suis sûr qu'il y aurait quelque chose de similaire en Java.


IIRC, vous obtenez une fin, dans la version .Net. :-(

1
Juste testé, pas de délimiteur de fin en .NET ou Python
tarn

1
Merci! J'en ai trouvé la source: svn.python.org/view/python/branches/release22-branch/Objects / ... recherchez "Catenate Everything". Il omet le séparateur du dernier élément.
13ren

@ 13ren Bien fait! Aide-t-il? J'avais un verrou et je me suis rapidement rappelé pourquoi je choisis de ne pas programmer en C ces jours-ci: P
tarn

@tarn, ouais, c'est un exemple intéressant car il ajoute seulement la virgule si ce n'est pas le dernier élément: if (i <seqlen - 1) {<add separator>}
13ren

0

Vous pouvez également ajouter de manière inconditionnelle la chaîne de délimitation et, après la boucle, supprimer le délimiteur supplémentaire à la fin. Puis un "si la liste est vide alors renvoyer cette chaîne" au début vous permettra d'éviter la vérification à la fin (car vous ne pouvez pas supprimer des caractères d'une liste vide)

La question est donc vraiment:

"Étant donné une boucle et un si, selon vous, quelle est la manière la plus claire de les réunir?"


0
public static String join (List<String> list, String separator) {
  String listToString = "";

  if (list == null || list.isEmpty()) {
   return listToString;
  }

  for (String element : list) {
   listToString += element + separator;
  }

  listToString = listToString.substring(0, separator.length());

  return listToString;
}

1
Je pense que vous vouliez direlistToString.substring(0, listToString.length()-separator.length());
13ren

0
if (array.length>0)          // edited in response Joachim's comment
  sb.append(array[i]);
for (int i=1; i<array.length; i++)
  sb.append(",").append(array[i]);

Basé sur la manière la plus claire de délimiter une liste par des virgules (Java)?

En utilisant cette idée: le dernier élément d'une boucle mérite-t-il un traitement séparé?


1
Pour que cela fonctionne, vous devez savoir qu'il y a au moins 1 élément dans la liste, sinon votre première boucle for plantera avec une IndexOutOfBoundsException.
Joachim H. Skeie

@Joachim merci, vous avez raison bien sûr. Quelle solution moche j'ai écrit! Je vais changer for (int i=0; i<1; i++)pour if (array.length>0).
13ren

0
public String toString(List<Item> items)
{
    StringBuilder sb = new StringBuilder("[");

    for (Item item : items)
    {
        sb.append(item).append(", ");
    }

    if (sb.length() >= 2)
    {
        //looks cleaner in C# sb.Length -= 2;
        sb.setLength(sb.length() - 2);
    }

    sb.append("]");

    return sb.toString();
}

1
Je viens de remarquer que c'est similaire à la réponse donnée par TofuBeer
Eblis

0

Aucune des réponses n'utilise la récursivité jusqu'à présent ...

public class Main {

    public static String toString(List<String> list, char d) {
        int n = list.size();
        if(n==0) return "";
        return n > 1 ? Main.toString(list.subList(0, n - 1), d) + d
                  + list.get(n - 1) : list.get(0);
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList(new String[]{"1","2","3"});
        System.out.println(Main.toString(list, ','));
    }

}

0
private String betweenComma(ArrayList<String> strings) {
    String united = "";
    for (String string : strings) {
        united = united + "," + string;
    }
    return united.replaceFirst(",", "");
}

-1

Méthode

String join(List<Object> collection, String delimiter){
    StringBuilder stringBuilder = new StringBuilder();
    int size = collection.size();
    for (Object value : collection) {
        size --;
        if(size > 0){
            stringBuilder.append(value).append(delimiter);
        }
    }

    return stringBuilder.toString();
}

Usage

Étant donné le tableau de [1,2,3]

join(myArray, ",") // 1,2,3
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.