2009-02-07 6 views
105

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

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

// add elements 

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

Je trouve en quelque sorte que ce soit plus propre que d'utiliser l'approche StringBuilder/StringBuffer.

Un commentaire?

Répondre

42

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 des chances qu'elle le soit. Il est beaucoup plus fiable de l'implémenter vous-même (boucles, StringBuilders, récursion ce que vous préférez). Bien sûr, cette approche peut sembler "plus soignée" ou plus "trop ​​douce" ou "argent", mais c'est, à mon avis, une approche pire.

+8

Je suis d'accord pour dire que je n'aime pas la façon de faire de l'OP, mais pour des raisons différentes. (1) Faire ainsi déguise l'intention du code (alors que quelque chose comme 'StringUtils.join' est parfaitement lisible). (2) Ce n'est pas flexible, puisque vous pourriez vouloir changer le délimiteur. (Remplacer simplement "", "' dans la chaîne finale n'est pas un bon moyen, car il pourrait y avoir '", "' incorporé dans les chaînes d'origine.) Donc même si, comme vous le dites, 'ArrayList.toString' a probablement gagné Je ne changerais pas, je n'irais toujours pas dans cette voie. –

+2

Vous pouvez être sûr à 99,9% que ArrayList.toString ne changera pas à moins de devenir un vecteur d'attaque; la rétrocompatibilité verra à cela. Mais je suis d'accord qu'utiliser toString() est toujours une mauvaise idée. –

2

Je trouve en quelque sorte que ce soit plus propre que en utilisant l'approche StringBuilder/StringBuffer .

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

La méthode AbstractCollection # toString() parcourt simplement tous les éléments et les ajoute à un StringBuilder. Donc, votre méthode peut sauver quelques lignes de code, mais au prix de manipulations supplémentaires. Que ce compromis soit bon, c'est à vous de décider.

+0

La manipulation de chaîne supplémentaire dont vous parlez est la création sousChaîne à la fin, non? – Jagmal

+0

@Jagmal, oui. Si ce code s'exécute beaucoup, vous devez le profiler pour voir si c'est un problème pour vous. Sinon, vous devriez être OK. Une chose à garder à l'esprit est que le format de l'interne toString() pourrait changer dans une future version de JDK. – Kevin

+0

@Kevin: Oui, l'implémentation de toString changeant dans la future version de JDK semble assez bonne pour ne pas le faire, je suppose. :) – Jagmal

0

Selon le besoin de performance et la quantité d'éléments à ajouter, cela pourrait être une bonne solution. Si la quantité d'éléments est élevée, la réallocation de la mémoire de Arraylist peut être un peu plus lente que StringBuilder.

+0

Pouvez-vous expliquer "la réallocation de mémoire de Arraylists pourrait être un peu plus lent que StringBuilder." – Jagmal

+0

Arraylist est construit sur des tableaux standard et ceux-ci vous oblige à définir une taille du tableau (comme 'int [] a = new int [10];'). C'est ce que l'ArrayList fait pour vous et lorsque vous mettez de nouveaux éléments, l'ArrayList devra faire un plus grand tableau et copier tous les éléments sur le nouveau. – georg

1

ArrayList hérite toString() -method 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 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 rejoindre efficacement, par exemple comme ceci:

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

J'espère que vous avez fait des benchmarks pour confirmer que votre implémentation est meilleure car elle n'est pas vraiment plus propre que l'implémentation toString de AbstractCollection. Le lecteur doit vraiment se concentrer sur votre implémentation pour comprendre quelque chose tandis que l'autre est vraiment facile à lire. –

4

Avez-vous vu ce billet de blog Coding Horror?

The Sad Tragedy of Micro-Optimization Theater

Je ne suis pas shure si oui ou non il est « plus propre », mais d'une performance point de vue, il ne sera probablement pas beaucoup d'importance.

2

Il me semble que le 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 il est probablement), 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 qui ont répondu à cette question quand ils disent que vous ne devriez pas compter sur la méthode toString() de Java ArrayList.

39

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

Slick! Je n'ai jamais vu la solution dans cette optique. Très agréable. – codefin

+1

@codefine Voir http://stackoverflow.com/questions/58431/algorithm-for-joining-eg-an-array-of-strings – Jagmal

+0

J'aime vraiment cette réponse b/c vous pouvez utiliser un foreach et c'est très simple mais c'est aussi plus inefficace. Comment pouvez-vous faire une boucle plus serrée? – Jess

252

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 êtes assez chanceux pour être en Java 8, il est encore plus facile ... il suffit d'utiliser String.join

String result = String.join(", ", list); 
+1

Alternativement pour les utilisateurs de Guava: 'Joiner.on (',').join (Collection) ' – froginvasion

+2

Yup, à chaque fois: Bloch," Java efficace ", Item 47:" Connaître et utiliser les bibliothèques ". Les bibliothèques Apache Commons devraient être la première chose à mettre dans votre fichier de construction (espérons Gradle). Comme le conclut le point 47: "Pour résumer, ne réinventez pas la roue". –

+2

Utilisation de l'API Goyave:. 'Joiner.on ('') .skipNulls() join (Iterable )'. Pour représenter les valeurs nulles, vous pouvez utiliser 'Joiner.on (','). UseForNull (" # "). Join (Iterable )' – savanibharat

0

Utilisation de la bibliothèque Functional Java, les importer:

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

... alors vous pouvez le faire:

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

Ou, la manière générique, si vous ne disposez pas d'une liste de chaînes:

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

En supposant qu'il est plus rapide de se déplacer juste un pointeur/définir un octet nul (ou cependant Java implémente StringBuilder # setLength), plutôt que de vérifier une condition à chaque fois dans la boucle pour voir quand ajouter le séparateur, vous pouvez utiliser cette méthode:

public static String Intersperse (Collection<?> collection, String delimiter) 
{ 
    StringBuilder sb = new StringBuilder(); 
    for (Object item : collection) 
    { 
     if (item == null) continue; 
     sb.append (item).append (delimiter); 
    } 
    sb.setLength (sb.length() - delimiter.length()); 
    return sb.toString(); 
}
15

Guava est une bibliothèque assez nette de Google:

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

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

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

Utilisation de Java 8+

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

ou même

String str = String.join("", list); 
4

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

18

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

0

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

1

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

plutôt que de dépendre de la mise en œuvre ArrayList.toString(), vous pouvez écrire un, en une ligne si vous utilisez java 8:

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

Si vous préférez utiliser StringBuffer au lieu de chaîne depuis String::concat a une durée d'exécution de O(n^2), vous pouvez convertir tous les String-StringBuffer en premier.

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

Pour cette implémentation est O (n^2) car String :: concat copie les deux chaînes sur un nouveau tampon: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java#String.concat%28java.lang.String% 29 – tep

11

Ceci est la façon la plus élégante et propre que je l'ai trouvé à ce jour:

list.stream().collect(Collectors.joining(delimiter)); 
Questions connexes