web-dev-qa-db-fra.com

Meilleur moyen de concaténer une liste d'objets String?

Quel est le meilleur moyen de concaténer une liste d'objets String? Je pense faire de cette façon:

List<String> sList = new ArrayList<String>();

// add elements

if (sList != null)
{
    String listString = sList.toString();
    listString = listString.subString(1, listString.length() - 1);
}

J'ai en quelque sorte trouvé que cela était plus pratique que d'utiliser l'approche StringBuilder/StringBuffer.

Des pensées/commentaires?

123
Jagmal

Votre approche dépend de l'implémentation ArrayList # toString () de Java.

Bien que l'implémentation soit documentée dans l'API Java et très peu susceptible de changer, il y a une chance pour que cela se produise. Il est bien plus fiable de le mettre en œuvre vous-même (boucles, StringBuilders, récursivité, tout ce que vous préférez).

Bien sûr, cette approche peut sembler "plus propre" ou plus "trop ​​douce" ou "d'argent" mais c'est, à mon avis, une approche pire.

43
Jack Leow

Utilisez l’une des méthodes StringUtils.join dans Apache Commons Lang.

import org.Apache.commons.lang3.StringUtils;

String result = StringUtils.join(list, ", ");

Si vous avez la chance d'utiliser Java 8, c'est encore plus simple ... utilisez simplement String.join

String result = String.join(", ", list);
299
Jeff Olson

Utiliser Java 8+

String str = list.stream().collect(Collectors.joining())

ou même 

String str = String.join("", list);
122
Andrei N

Une variante de la réponse de codefin

public static String concatStringsWSep(Iterable<String> strings, String separator) {
    StringBuilder sb = new StringBuilder();
    String sep = "";
    for(String s: strings) {
        sb.append(sep).append(s);
        sep = separator;
    }
    return sb.toString();                           
}
41
Peter Lawrey

Si vous développez pour Android, il existe TextUtils.join fourni par le SDK.

25
Chunliang Lyu

C'est la manière la plus élégante et la plus propre que j'ai trouvée jusqu'à présent:

list.stream().collect(Collectors.joining(delimiter));
16

Guava est une jolie bibliothèque de Google:

Joiner joiner = Joiner.on(", ");
joiner.join(sList);
15
andras

Avez-vous vu cette entrée de blog Coding Horror?

La triste tragédie du théâtre de la micro-optimisation

Je ne suis pas sûr que ce soit ou non "plus ordonné", mais du point de vue de la performance, cela n'aura probablement pas beaucoup d'importance.

4
sdfx

Je préfère String.join (liste) en Java 8

4
Manish Mulani

Plutôt que de dépendre de l'implémentation ArrayList.toString(), vous pouvez écrire une ligne, si vous utilisez Java 8:

String result = sList.stream()
                     .reduce("", String::concat);

Si vous préférez utiliser StringBuffer au lieu de String puisque String::concat a une exécution de O(n^2), vous pouvez convertir chaque String en StringBuffer en premier.

StringBuffer result = sList.stream()
                           .map(StringBuffer::new)
                           .reduce(new StringBuffer(""), StringBuffer::append);
3
Sreekanth

J'ai en quelque sorte trouvé cela plus net que en utilisant le StringBuilder/StringBuffer approche.

Je suppose que cela dépend de l'approche que vous avez adoptée. 

La méthode AbstractCollection # toString () itère simplement tous les éléments et les ajoute à un StringBuilder. Donc, votre méthode peut sauver quelques lignes de code mais au prix d’une manipulation supplémentaire de String. Que ce compromis soit un bon choix dépend de vous.

3
Kevin

En supposant qu'il soit plus rapide de simplement déplacer un pointeur/définir un octet sur null (ou que Java implémente StringBuilder # setLength) plutôt que de vérifier une condition à chaque fois dans la boucle pour savoir quand ajouter le délimiteur, vous pouvez utiliser cette méthode:

public static String Intersperse (Collection <?> collection, délimiteur de chaîne) 
 {
 StringBuilder sb = new StringBuilder (); 
 pour (objet: collection) 
 {
 if (item == null) continue; 
 sb.append (item) .append (délimiteur); 
 } 
 sb.setLength (sb.length () - delimiter.length ()); 
 retourne sb.toString (); 
}
2
MonkeeSage

ArrayList hérite de sa méthode toString()- de AbstractCollection , à savoir:

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

Construire la chaîne vous-même sera beaucoup plus efficace.


Si vous voulez vraiment regrouper les chaînes au préalable dans une sorte de liste, vous devez fournir votre propre méthode pour les joindre efficacement, par exemple. comme ça:

static String join(Collection<?> items, String sep) {
    if(items.size() == 0)
        return "";

    String[] strings = new String[items.size()];
    int length = sep.length() * (items.size() - 1);

    int idx = 0;
    for(Object item : items) {
        String str = item.toString();
        strings[idx++] = str;
        length += str.length();
    }

    char[] chars = new char[length];
    int pos = 0;

    for(String str : strings) {
        str.getChars(0, str.length(), chars, pos);
        pos += str.length();

        if(pos < length) {
            sep.getChars(0, sep.length(), chars, pos);
            pos += sep.length();
        }
    }

    return new String(chars);
}
2
Christoph

Variation suivante de la réponse de Peter Lawrey sans initialisation d'une nouvelle chaîne à chaque tour de boucle

String concatList(List<String> sList, String separator)
{
    Iterator<String> iter = sList.iterator();
    StringBuilder sb = new StringBuilder();

    while (iter.hasNext())
    {
        sb.append(iter.next()).append( iter.hasNext() ? separator : "");
    }
    return sb.toString();
}
2
user2979550

Il me semble que StringBuilder sera rapide et efficace.

La forme de base ressemblerait à quelque chose comme ceci:

public static String concatStrings(List<String> strings)
{
    StringBuilder sb = new StringBuilder();
    for(String s: strings)
    {
        sb.append(s);
    }
    return sb.toString();       
}

Si c'est trop simpliste (et c'est probablement le cas), vous pouvez utiliser une approche similaire et ajouter un séparateur comme celui-ci:

    public static String concatStringsWSep(List<String> strings, String separator)
{
    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < strings.size(); i++)
    {
        sb.append(strings.get(i));
        if(i < strings.size() - 1)
            sb.append(separator);
    }
    return sb.toString();               
}

Je suis d’accord avec les autres personnes qui ont répondu à cette question en affirmant qu’il ne fallait pas compter sur la méthode toString () de ArrayList de Java.

2
codefin

si vous avez json dans vos dépendances, vous pouvez utiliser new JSONArray(list).toString()

0
programer8

En fonction des besoins en performances et de la quantité d'éléments à ajouter, cette solution peut être appropriée. Si la quantité d'éléments est élevée, la réaffectation de la mémoire par Arraylists peut être un peu plus lente que StringBuilder.

0
georg

En Java 8, vous pouvez également utiliser un réducteur, quelque chose comme:

public static String join(List<String> strings, String joinStr) {
    return strings.stream().reduce("", (prev, cur) -> prev += (cur + joinStr));
}
0

A l’aide de la bibliothèque Functional Java , importez ces éléments:

import static fj.pre.Monoid.stringMonoid;
import static fj.data.List.list;
import fj.data.List;

... alors vous pouvez faire ceci:

List<String> ss = list("foo", "bar", "baz");
String s = stringMonoid.join(ss, ", ");

Ou, de manière générique, si vous n'avez pas de liste de chaînes:

public static <A> String showList(List<A> l, Show<A> s) {
  return stringMonoid.join(l.map(s.showS_()), ", ");
}
0
Apocalisp