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

StackOverflow https://stackoverflow.com/questions/63150

  •  09-06-2019
  •  | 
  •  

Question

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 était quelque chose comme 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 partout, mais je voulais plus de clarté que d'optimisation.

En Ruby, je peux faire quelque chose comme ceci, 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, quel est le meilleur moyen de faire cela en Java?

Était-ce utile?

La solution

Pré Java 8:

Communs 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 directement via StringJoiner et String.join(). Les extraits ci-dessous montrent comment les utiliser:

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

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

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

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

<=>

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"

Autres conseils

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

Ensuite, utilisez-le comme suit:

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

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

    join(list, ",");

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

pour cela.
android.text.TextUtils.join(CharSequence delimiter, Iterable tokens)

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

La bibliothèque de Google goyave possède com.google.common.base.Joiner aide à résoudre de telles tâches.

Exemples:

"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 .

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 également cette réponse pour un exemple d'API Stream.

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

Cela pourrait fonctionner mieux.

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

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

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

Pourquoi StringBuffer au lieu de <= > ? A partir du <=> javadoc:

  

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.

dans Java 8, vous pouvez le faire comme suit:

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

si la liste a des valeurs NULL, vous pouvez utiliser:

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

J'utiliserais Google Collections. Il y a une belle installation 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.

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

Java 8

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

Vous pouvez utiliser le de Noreferrer"> StringBuilder tapez pour cela. Il y a aussi StringBuffer, mais il contient une logique de sécurité de thread supplémentaire qui est souvent inutile.

Et un minimum (si vous ne souhaitez pas inclure Apache Commons ou Gauva dans les dépendances de projets simplement pour rejoindre 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();
}

Utiliser StringBuilder et la classe Separator

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

Separator encapsule un délimiteur. Le séparateur est renvoyé par la méthode toString de Separator, sauf si le premier appel renvoie la chaîne vide!

Code source pour la classe <=>

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

}

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.

Si vous utilisez Spring MVC, essayez 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());

Cela se traduira par a,b,c

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

String retourne une toString() représentation semblable à makeString(start, separator, end).

Il a trois formes

  • makeString(separator)
  • ", " Les valeurs par défaut commencent et finissent par des chaînes vides
  • Appendable définit par défaut le séparateur sur StringBuilder (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("[", ", ", "]"));

void est similaire à <=>, mais il est ajouté à un <=> (comme <=>) et est <=>. Il a les trois mêmes formes, avec un premier argument supplémentaire, l’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.

Vous devriez probablement utiliser StringBuilder avec la méthode append pour construire votre résultat, mais sinon, cette solution est aussi performante que Java peut l'offrir.

Pourquoi ne faites-vous pas en Java la même chose que vous faites en 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 pouvez déplacer cette boucle for dans une méthode d'assistance distincte et utiliser également StringBuilder au lieu de StringBuffer ...

Modifier : correction de l'ordre des ajouts.

Avec les arguments de variable Java 5, vous n'avez donc pas à 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"));
    }
}

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

Il existe de nombreux raccourcis utiles tels que:

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

et beaucoup d'autres.

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

Je le préfère contre Apache Commons (bien que très bon 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);

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

Vous pouvez essayer quelque chose comme ceci:

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

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

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

Ci-dessous, une approche plus générique si vous pouvez créer la liste de paramètres AVANT de définir un 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");
}

Votre approche n’est pas trop mauvaise, mais vous devriez utiliser un StringBuffer au lieu d’utiliser le signe +. Le + présente le gros inconvénient qu'une nouvelle instance String est créée 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é.

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.

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 qui consiste à utiliser StringBuilder au lieu de String, 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 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 ", " lors du second appel à ajouter avec 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.

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

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

1) Classe Extend List - et ajoutez-y la méthode de jointure. La méthode join se chargerait simplement de concaténer et d’ajouter le délimiteur (qui pourrait être un paramètre de la méthode join)

2) Il semble que Java 7 va ajouter 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 en tant que méthode d'extension à List. ou même à Collection.

La solution 1 est probablement la seule solution réaliste, maintenant, bien que Java 7 ne soit pas encore disponible :), 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".

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top