web-dev-qa-db-fra.com

Quel est le meilleur moyen de créer une chaîne d'éléments délimités en Java?

Alors que je travaillais dans une application Java, j’ai récemment eu besoin d’assembler une liste de valeurs délimitées par des virgules à transmettre à un autre service Web sans savoir combien il y aurait d’éléments à l’avance. Le meilleur que je pouvais trouver de mémoire, ressemblait à ceci:

public String appendWithDelimiter( String original, String addition, String delimiter ) {
    if ( original.equals( "" ) ) {
        return addition;
    } else {
        return original + delimiter + addition;
    }
}

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Je réalise que ce n'est pas particulièrement efficace, car des chaînes sont créées un peu partout, mais je visais plus la clarté que l'optimisation.

En Ruby, je peux faire quelque chose comme ça à la place, qui se sent beaucoup plus élégant:

parameterArray = [];
parameterArray << "elementName" if condition;
parameterArray << "anotherElementName" if anotherCondition;
parameterString = parameterArray.join(",");

Mais comme Java ne dispose pas d'une commande de jointure, je n'ai rien trouvé d'équivalent.

Alors, quelle est la meilleure façon de faire cela en Java?

292
Sean McMains

Pre Java 8:

Commache lang d'Apache est votre ami ici - il fournit une méthode de jointure très similaire à celle que vous mentionnez dans Ruby:

StringUtils.join(Java.lang.Iterable,char)


Java 8:

Java 8 permet de se connecter par l’intermédiaire de StringJoiner et String.join(). Les extraits ci-dessous montrent comment vous pouvez les utiliser:

StringJoiner

StringJoiner joiner = new StringJoiner(",");
joiner.add("01").add("02").add("03");
String joinedString = joiner.toString(); // "01,02,03"

String.join(CharSequence delimiter, CharSequence... elements))

String joinedString = String.join(" - ", "04", "05", "06"); // "04 - 05 - 06"

String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

List<String> strings = new LinkedList<>();
strings.add("Java");strings.add("is");
strings.add("cool");
String message = String.join(" ", strings);
//message returned is: "Java is cool"
503
Martin Gladdish

Vous pourriez écrire une petite méthode utilitaire de type jointure qui fonctionne sur Java.util.Lists

public static String join(List<String> list, String delim) {

    StringBuilder sb = new StringBuilder();

    String loopDelim = "";

    for(String s : list) {

        sb.append(loopDelim);
        sb.append(s);            

        loopDelim = delim;
    }

    return sb.toString();
}

Alors utilisez-le comme suit:

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

    if( condition )        list.add("elementName");
    if( anotherCondition ) list.add("anotherElementName");

    join(list, ",");
52
Rob Dickerson

Dans le cas d’Android, la classe StringUtils de commons n’est pas disponible, aussi j’ai utilisé

Android.text.TextUtils.join(CharSequence delimiter, Iterable tokens)

http://developer.Android.com/reference/Android/text/TextUtils.html

47
Kevin

Le bibliothèque Google de Google a com.google.common.base.Joiner classe qui aide à résoudre de telles tâches.

Échantillons:

"My pets are: " + Joiner.on(", ").join(Arrays.asList("rabbit", "parrot", "dog")); 
// returns "My pets are: rabbit, parrot, dog"

Joiner.on(" AND ").join(Arrays.asList("field1=1" , "field2=2", "field3=3"));
// returns "field1=1 AND field2=2 AND field3=3"

Joiner.on(",").skipNulls().join(Arrays.asList("London", "Moscow", null, "New York", null, "Paris"));
// returns "London,Moscow,New York,Paris"

Joiner.on(", ").useForNull("Team held a draw").join(Arrays.asList("FC Barcelona", "FC Bayern", null, null, "Chelsea FC", "AC Milan"));
// returns "FC Barcelona, FC Bayern, Team held a draw, Team held a draw, Chelsea FC, AC Milan"

Voici un article sur les utilitaires de chaîne de Guava .

31
Alex K

Dans Java 8, vous pouvez utiliser String.join() :

List<String> list = Arrays.asList("foo", "bar", "baz");
String joined = String.join(" and ", list); // "foo and bar and baz"

Regardez aussi cette réponse pour un exemple d'API Stream.

25
micha

Vous pouvez le généraliser, mais il n'y a pas de jointure en Java, comme vous le dites bien.

This pourrait mieux fonctionner.

public static String join(Iterable<? extends CharSequence> s, String delimiter) {
    Iterator<? extends CharSequence> iter = s.iterator();
    if (!iter.hasNext()) return "";
    StringBuilder buffer = new StringBuilder(iter.next());
    while (iter.hasNext()) buffer.append(delimiter).append(iter.next());
    return buffer.toString();
}
20
Vinko Vrsalovic

Utilisez une approche basée sur Java.lang.StringBuilder ! ("Une séquence de caractères mutable.")

Comme vous l'avez mentionné, toutes ces concaténations de chaînes créent des chaînes partout. StringBuilder ne fera pas cela.

Pourquoi StringBuilder au lieu de StringBuffer ? Depuis le javadoc StringBuilder:

Dans la mesure du possible, il est recommandé d'utiliser cette classe plutôt que StringBuffer, car elle sera plus rapide dans la plupart des mises en œuvre.

15
Stu Thompson

dans Java 8, vous pouvez le faire comme ceci:

list.stream().map(Object::toString)
                        .collect(Collectors.joining(delimiter));

si list a null, vous pouvez utiliser:

list.stream().map(String::valueOf)
                .collect(Collectors.joining(delimiter))
14
gladiator

Je voudrais utiliser Google Collections. Il existe une installation Nice Join.
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html

Mais si je voulais l'écrire moi-même,

package util;

import Java.util.ArrayList;
import Java.util.Iterable;
import Java.util.Collections;
import Java.util.Iterator;

public class Utils {
    // accept a collection of objects, since all objects have toString()
    public static String join(String delimiter, Iterable<? extends Object> objs) {
        if (objs.isEmpty()) {
            return "";
        }
        Iterator<? extends Object> iter = objs.iterator();
        StringBuilder buffer = new StringBuilder();
        buffer.append(iter.next());
        while (iter.hasNext()) {
            buffer.append(delimiter).append(iter.next());
        }
        return buffer.toString();
    }

    // for convenience
    public static String join(String delimiter, Object... objs) {
        ArrayList<Object> list = new ArrayList<Object>();
        Collections.addAll(list, objs);
        return join(delimiter, list);
    }
}

Je pense que cela fonctionne mieux avec une collection d'objets, car vous n'avez plus besoin de convertir vos objets en chaînes avant de les rejoindre.

10
Eric Normand

Apache commons La classe StringUtils a une méthode de jointure.

8
Alejandro

Java 8

stringCollection.stream().collect(Collectors.joining(", "));
5
gstackoverflow

Et un minimum (si vous ne voulez pas inclure Apache Commons vs Guava dans les dépendances de projet juste pour joindre des chaînes)

/**
 *
 * @param delim : String that should be kept in between the parts
 * @param parts : parts that needs to be joined
 * @return  a String that's formed by joining the parts
 */
private static final String join(String delim, String... parts) {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < parts.length - 1; i++) {
        builder.append(parts[i]).append(delim);
    }
    if(parts.length > 0){
        builder.append(parts[parts.length - 1]);
    }
    return builder.toString();
}
3
Thamme Gowda

Utilisez StringBuilder et la classe Separator

StringBuilder buf = new StringBuilder();
Separator sep = new Separator(", ");
for (String each : list) {
    buf.append(sep).append(each);
}

Le séparateur enveloppe un délimiteur. Le délimiteur est renvoyé par la méthode toString du séparateur, sauf lors du premier appel renvoyant la chaîne vide!

Code source pour la classe Separator

public class Separator {

    private boolean skipFirst;
    private final String value;

    public Separator() {
        this(", ");
    }

    public Separator(String value) {
        this.value = value;
        this.skipFirst = true;
    }

    public void reset() {
        skipFirst = true;
    }

    public String toString() {
        String sep = skipFirst ? "" : value;
        skipFirst = false;
        return sep;
    }

}
3
akuhn

Vous pouvez utiliser le type StringBuilder de Java pour cela. Il existe également StringBuffer, mais il contient une logique de sécurité de thread supplémentaire qui est souvent inutile.

3
Kent Boogaart

Si vous utilisez Eclipse Collections , vous pouvez utiliser makeString() ou appendString().

makeString() renvoie une représentation String, similaire à toString().

Il a trois formes

  • makeString(start, separator, end)
  • makeString(separator) par défaut, début et fin des chaînes vides
  • makeString() définit par défaut le séparateur sur ", " (virgule et espace)

Exemple de code:

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
assertEquals("[1/2/3]", list.makeString("[", "/", "]"));
assertEquals("1/2/3", list.makeString("/"));
assertEquals("1, 2, 3", list.makeString());
assertEquals(list.toString(), list.makeString("[", ", ", "]"));

appendString() est similaire à makeString(), mais il est ajouté à un Appendable (comme StringBuilder) et est void. Il a les trois mêmes formes, avec un premier argument supplémentaire, Appendable.

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
Appendable appendable = new StringBuilder();
list.appendString(appendable, "[", "/", "]");
assertEquals("[1/2/3]", appendable.toString());

Si vous ne pouvez pas convertir votre collection en un type de collections Eclipse, adaptez-la simplement avec l'adaptateur approprié.

List<Object> list = ...;
ListAdapter.adapt(list).makeString(",");

Remarque: Je suis un partisan des collections Eclipse.

2
Craig P. Motlin

Si vous utilisez Spring MVC, vous pouvez essayer les étapes suivantes.

import org.springframework.util.StringUtils;

List<String> groupIds = new List<String>;   
groupIds.add("a");    
groupIds.add("b");    
groupIds.add("c");

String csv = StringUtils.arrayToCommaDelimitedString(groupIds.toArray());

Il en résultera à a,b,c

2
Ankit Lalan

Pourquoi ne pas écrire votre propre méthode join ()? Cela prendrait comme paramètres une collection de Strings et un délimiteur de String. Dans la méthode, parcourez la collection et construisez votre résultat dans un StringBuffer.

2
Dave Costa

Avec Java 5 arguments variables, vous n'avez donc pas besoin d'insérer explicitement toutes vos chaînes dans une collection ou un tableau:

import junit.framework.Assert;
import org.junit.Test;

public class StringUtil
{
    public static String join(String delim, String... strings)
    {
        StringBuilder builder = new StringBuilder();

        if (strings != null)
        {
            for (String str : strings)
            {
                if (builder.length() > 0)
                {
                    builder.append(delim).append(" ");
                }
                builder.append(str);
            }
        }           
        return builder.toString();
    }
    @Test
    public void joinTest()
    {
        Assert.assertEquals("", StringUtil.join(",", null));
        Assert.assertEquals("", StringUtil.join(",", ""));
        Assert.assertEquals("", StringUtil.join(",", new String[0]));
        Assert.assertEquals("test", StringUtil.join(",", "test"));
        Assert.assertEquals("foo, bar", StringUtil.join(",", "foo", "bar"));
        Assert.assertEquals("foo, bar, x", StringUtil.join(",", "foo", "bar", "x"));
    }
}
1
Mark B

Type natif Java 8

List<Integer> example;
example.add(1);
example.add(2);
example.add(3);
...
example.stream().collect(Collectors.joining(","));

Objet personnalisé Java 8:

List<Person> person;
...
person.stream().map(Person::getAge).collect(Collectors.joining(","));
1
Luis Aguilar

Pour ceux qui se trouvent dans un contexte Spring, leur classe StringUtils est également utile:

Il y a beaucoup de raccourcis utiles comme:

  • collectionToCommaDelimitedString (Collection coll)
  • collectionToDelimitedString (Collection coll, String delim)
  • arrayToDelimitedString (Object [] arr, String delim)

et plein d'autres.

Cela peut être utile si vous n'utilisez pas déjà Java 8 et que vous vous trouvez déjà dans un contexte Spring.

Je le préfère aux Apache Commons (bien que très bien aussi) pour le support de la Collection qui est plus facile comme ceci:

// Encoding Set<String> to String delimited 
String asString = org.springframework.util.StringUtils.collectionToDelimitedString(codes, ";");

// Decoding String delimited to Set
Set<String> collection = org.springframework.util.StringUtils.commaDelimitedListToSet(asString);
1
рüффп

Vous devriez probablement utiliser un StringBuilder avec la méthode append pour construire votre résultat, mais sinon, cette solution est aussi bonne qu'une solution que Java a à offrir.

1
newdayrising

Pourquoi ne faites-vous pas dans Java la même chose que vous faites dans Ruby, c'est-à-dire que vous créez la chaîne séparée par un délimiteur uniquement après avoir ajouté tous les éléments au tableau?

ArrayList<String> parms = new ArrayList<String>();
if (someCondition) parms.add("someString");
if (anotherCondition) parms.add("someOtherString");
// ...
String sep = ""; StringBuffer b = new StringBuffer();
for (String p: parms) {
    b.append(sep);
    b.append(p);
    sep = "yourDelimiter";
}

Vous voudrez peut-être déplacer cette boucle for dans une méthode d'assistance distincte et utiliser également StringBuilder au lieu de StringBuffer ...

Edit: corrige l'ordre des ajouts.

1
agnul

Donc, fondamentalement, quelque chose comme ceci:

public static String appendWithDelimiter(String original, String addition, String delimiter) {

if (original.equals("")) {
    return addition;
} else {
    StringBuilder sb = new StringBuilder(original.length() + addition.length() + delimiter.length());
        sb.append(original);
        sb.append(delimiter);
        sb.append(addition);
        return sb.toString();
    }
}
0
killdash10

Je ne sais pas si c'est vraiment mieux, mais au moins, il utilise StringBuilder, ce qui peut être légèrement plus efficace.

Vous trouverez ci-dessous une approche plus générique si vous pouvez construire la liste de paramètres AVANT de procéder à une délimitation de paramètre.

// Answers real question
public String appendWithDelimiters(String delimiter, String original, String addition) {
    StringBuilder sb = new StringBuilder(original);
    if(sb.length()!=0) {
        sb.append(delimiter).append(addition);
    } else {
        sb.append(addition);
    }
    return sb.toString();
}


// A more generic case.
// ... means a list of indeterminate length of Strings.
public String appendWithDelimitersGeneric(String delimiter, String... strings) {
    StringBuilder sb = new StringBuilder();
    for (String string : strings) {
        if(sb.length()!=0) {
            sb.append(delimiter).append(string);
        } else {
            sb.append(string);
        }
    }

    return sb.toString();
}

public void testAppendWithDelimiters() {
    String string = appendWithDelimitersGeneric(",", "string1", "string2", "string3");
}
0
Mikezx6r

Votre approche n’est pas trop mauvaise, mais vous devriez utiliser un StringBuffer au lieu d’utiliser le signe +. Le + a le gros inconvénient de créer une nouvelle instance String pour chaque opération. Plus votre chaîne est longue, plus les frais généraux sont importants. Donc, utiliser un StringBuffer devrait être le moyen le plus rapide:

public StringBuffer appendWithDelimiter( StringBuffer original, String addition, String delimiter ) {
        if ( original == null ) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(addition);
                return buffer;
        } else {
                buffer.append(delimiter);
                buffer.append(addition);
                return original;
        }
}

Après avoir fini de créer votre chaîne, appelez simplement toString () sur le StringBuffer renvoyé.

0
Yaba
//Note: if you have access to Java5+, 
//use StringBuilder in preference to StringBuffer.  
//All that has to be replaced is the class name.  
//StringBuffer will work in Java 1.4, though.

appendWithDelimiter( StringBuffer buffer, String addition, 
    String delimiter ) {
    if ( buffer.length() == 0) {
        buffer.append(addition);
    } else {
        buffer.append(delimiter);
        buffer.append(addition);
    }
}


StringBuffer parameterBuffer = new StringBuffer();
if ( condition ) { 
    appendWithDelimiter(parameterBuffer, "elementName", "," );
}
if ( anotherCondition ) {
    appendWithDelimiter(parameterBuffer, "anotherElementName", "," );
}

//Finally, to return a string representation, call toString() when returning.
return parameterBuffer.toString(); 
0
MetroidFan2002

utiliser Dollar est simple:

String joined = $(aCollection).join(",");

NB: cela fonctionne aussi pour Array et d'autres types de données

La mise en oeuvre

En interne, il utilise un truc très astucieux:

@Override
public String join(String separator) {
    Separator sep = new Separator(separator);
    StringBuilder sb = new StringBuilder();

    for (T item : iterable) {
        sb.append(sep).append(item);
    }

    return sb.toString();
}

la classe Separator ne renvoie la chaîne vide que la première fois qu'elle est invoquée, puis renvoie le séparateur:

class Separator {

    private final String separator;
    private boolean wasCalled;

    public Separator(String separator) {
        this.separator = separator;
        this.wasCalled = false;
    }

    @Override
    public String toString() {
        if (!wasCalled) {
            wasCalled = true;
            return "";
        } else {
            return separator;
        }
    }
}
0
dfa

Réponse fixe Rob Dickerson.

C'est plus facile à utiliser:

public static String join(String delimiter, String... values)
{
    StringBuilder stringBuilder = new StringBuilder();

    for (String value : values)
    {
        stringBuilder.append(value);
        stringBuilder.append(delimiter);
    }

    String result = stringBuilder.toString();

    return result.isEmpty() ? result : result.substring(0, result.length() - 1);
}
0
maXp

Au lieu d'utiliser la concaténation de chaînes, vous devez utiliser StringBuilder si votre code n'est pas threadé et StringBuffer si c'est le cas.

0
Aaron

Il y a donc deux choses que vous pourriez faire pour avoir l'impression que vous recherchez:

1) Étendez la classe List - et ajoutez-y la méthode de jointure. La méthode join fait simplement le travail de concaténation et d’ajout du délimiteur (qui peut être un paramètre de la méthode join)

2) Il semble que Java 7 ajoute des méthodes d'extension à Java - ce qui vous permet simplement d'attacher une méthode spécifique à une classe: vous pouvez donc écrire cette méthode de jointure. et l'ajouter comme méthode d'extension à la liste ou même à la collection.

La solution 1 est probablement la seule solution réaliste, maintenant, bien que puisque Java 7 ne soit pas encore sorti :) Mais cela devrait fonctionner correctement.

Pour utiliser les deux, ajoutez simplement tous vos éléments à la liste ou à la collection, puis appelez la nouvelle méthode personnalisée pour les "joindre".

0
user13276

Vous rendez cela un peu plus compliqué que cela ne doit être. Commençons par la fin de votre exemple:

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Avec le petit changement d'utiliser un StringBuilder au lieu d'une chaîne, cela devient:

StringBuilder parameterString = new StringBuilder();
if (condition) parameterString.append("elementName").append(",");
if (anotherCondition) parameterString.append("anotherElementName").append(",");
...

Lorsque vous avez terminé (je suppose que vous devez également vérifier quelques autres conditions), assurez-vous simplement de supprimer la virgule arrière avec une commande comme celle-ci:

if (parameterString.length() > 0) 
    parameterString.deleteCharAt(parameterString.length() - 1);

Et enfin, obtenez la chaîne que vous voulez avec

parameterString.toString();

Vous pouvez également remplacer le "," dans le deuxième appel à ajouter par une chaîne de délimiteur générique pouvant être définie sur n'importe quoi. Si vous avez une liste d'éléments que vous savez que vous devez ajouter (de manière non conditionnelle), vous pouvez insérer ce code dans une méthode prenant une liste de chaînes.

0
cjw2600

Légère amélioration [vitesse] de la version de izb:

public static String join(String[] strings, char del)
{
    StringBuilder sb = new StringBuilder();
    int len = strings.length;

    if(len > 1) 
    {
       len -= 1;
    }else
    {
       return strings[0];
    }

    for (int i = 0; i < len; i++)
    {
       sb.append(strings[i]).append(del);
    }

    sb.append(strings[i]);

    return sb.toString();
}
0
java al

Personnellement, j’utilise assez souvent la solution simple suivante pour la journalisation:

List lst = Arrays.asList("ab", "bc", "cd");
String str = lst.toString().replaceAll("[\\[\\]]", "");
0
Philipp Grigoryev

Vous pouvez essayer quelque chose comme ça:

StringBuilder sb = new StringBuilder();
if (condition) { sb.append("elementName").append(","); }
if (anotherCondition) { sb.append("anotherElementName").append(","); }
String parameterString = sb.toString();
0
martinatime