it-swarm-eu.dev

Qual è il modo migliore per costruire una stringa di elementi delimitati in Java?

Mentre lavoravo in un'app Java, di recente avevo bisogno di assemblare un elenco di valori delimitati da virgole per passare a un altro servizio web senza sapere quanti elementi ci sarebbero in anticipo. Il meglio che ho potuto ricavare dalla cima della mia testa era qualcosa del genere:

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

Mi rendo conto che questo non è particolarmente efficiente, dal momento che ci sono stringhe create dappertutto, ma io volevo fare chiarezza più che ottimizzare.

In Ruby, invece, posso fare qualcosa del genere, che sembra molto più elegante:

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

Ma dal momento che Java non ha un comando join, non sono riuscito a capire nulla di equivalente.

Quindi, qual è il modo migliore per farlo in Java?

280
Sean McMains

Pre Java 8:

Apache's commons lang è tuo amico qui - fornisce un metodo di join molto simile a quello a cui fai riferimento in Ruby:

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


Java 8:

Java 8 fornisce l'unione immediata tramite StringJoinere String.join(). I frammenti di seguito mostrano come puoi usarli:

StringJoinerNAME _

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"
490
Martin Gladdish

Potresti scrivere un piccolo metodo di utilità in stile join che funziona su 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();
}

Quindi usalo in questo modo:

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

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

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

Nel caso di Android, la classe StringUtils dei comuni non è disponibile, quindi per questo ho usato

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

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

46
Kevin

La libreria Guava di Google ha com.google.common. base.Joiner classe che aiuta a risolvere tali compiti.

Campioni:

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

Ecco un articolo sulle utility di stringa di Guava .

31
Alex K

In Java 8 puoi usare String.join() :

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

Dai anche un'occhiata a questa risposta per un esempio di Stream API.

25
micha

Puoi generalizzarlo, ma non c'è join in Java, come dici tu.

Questo potrebbe funzionare meglio.

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

Utilizza un approccio basato su Java.lang.StringBuilder ! ("Una sequenza mutevole di caratteri.")

Come hai detto tu, tutte quelle concatenazioni di stringhe stanno creando dappertutto stringhe. StringBuilder non lo farà.

Perché StringBuilder invece di StringBuffer ? Da StringBuilder javadoc:

Laddove possibile, si consiglia di utilizzare questa classe preferibilmente a StringBuffer in quanto sarà più veloce nella maggior parte delle implementazioni.

15
Stu Thompson

in Java 8 puoi fare questo:

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

se la lista ha valori null puoi usare:

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

Vorrei utilizzare Google Collections. C'è una struttura Nice Join.
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html

Ma se volessi scrivere da solo,

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

Penso che funzioni meglio con una collezione di oggetti, dato che ora non devi convertire i tuoi oggetti in stringhe prima di unirti a loro.

10
Eric Normand

Apache commons La classe StringUtils ha un metodo di join.

8
Alejandro

Java 8

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

E uno minimo (se non vuoi includere Apache Commons vs Guava nelle dipendenze del progetto solo per il piacere di unirti alle stringhe)

/**
 *
 * @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

Usa StringBuilder e classe Separator

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

Il separatore avvolge un delimitatore. Il delimitatore viene restituito dal metodo toString di Separator, a meno che nella prima chiamata non venga restituita la stringa vuota!

Codice sorgente per 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

Puoi usare il tipo di Java StringBuilder per questo. Esiste anche StringBuffer, ma contiene una logica di sicurezza aggiuntiva del thread che spesso non è necessaria.

3
Kent Boogaart

Se stai usando Eclipse Collections , puoi usare makeString() o appendString().

makeString() restituisce una rappresentazione String, simile a toString().

Ha tre forme

  • makeString(start, separator, end)
  • makeString(separator) i valori di default iniziano e terminano con stringhe vuote
  • makeString() imposta il separatore su ", " (virgola e spazio)

Esempio di codice:

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() è simile a makeString(), ma si aggiunge ad un Appendable (come StringBuilder) ed è void. Ha le stesse tre forme, con un primo argomento aggiuntivo, Appendable.

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

Se non riesci a convertire la tua collezione in un tipo di raccolta Eclipse, adattala semplicemente con l'adattatore appropriato.

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

Nota: Sono un committer per le raccolte di Eclipse.

2
Craig P. Motlin

Se si utilizza Spring MVC, è possibile provare i seguenti passaggi.

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

Risulterà a a,b,c

2
Ankit Lalan

Perché non scrivere il tuo metodo join ()? Avrebbe preso come raccolta parametri di Stringhe e una stringa delimitatore. All'interno del metodo, iterate sulla raccolta e costruite il risultato in un StringBuffer.

2
Dave Costa

Con Java 5 variabili args, quindi non devi inserire tutte le tue stringhe in una raccolta o array in modo esplicito:

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

Tipo nativo di Java 8

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

Oggetto personalizzato Java 8:

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

Per coloro che si trovano in un contesto di primavera è utile anche la loro StringUtils classe:

Ci sono molte scorciatoie utili come:

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

e molti altri.

Questo può essere utile se non stai già utilizzando Java 8 e sei già in un contesto Spring.

Lo preferisco contro l'Apache Commons (anche se molto buono) per il supporto della raccolta che è più facile in questo modo:

// 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
рüффп

Probabilmente dovresti usare un StringBuilder con il metodo append per costruire il tuo risultato, ma per il resto questa è la soluzione migliore che Java ha da offrire.

1
newdayrising

Perché non fai in Java la stessa cosa che stai facendo in Ruby, che sta creando la stringa delimitatore separata solo dopo aver aggiunto tutti i pezzi alla matrice?

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

Potresti voler spostare quello per il ciclo in un metodo di supporto separato, e usare anche StringBuilder invece di StringBuffer ...

Modifica : risolto l'ordine degli appendi.

1
agnul

Quindi praticamente qualcosa del genere:

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

Non so se questo è davvero meglio, ma almeno sta usando StringBuilder, che potrebbe essere leggermente più efficiente.

Di seguito è riportato un approccio più generico se è possibile creare l'elenco dei parametri PRIMA di eseguire qualsiasi delimitazione dei parametri.

// 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

Il tuo approccio non è male, ma dovresti usare un StringBuffer invece di usare il segno +. Il + ha il grande svantaggio che una nuova istanza String viene creata per ogni singola operazione. Più lunga è la corda, più grande sarà il sovraccarico. Quindi usare un StringBuffer dovrebbe essere il modo più veloce:

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

Dopo aver terminato di creare la stringa, chiama semplicemente toString () sul StringBuffer restituito.

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

using Dollar è semplice come digitare:

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

NB: funziona anche per Array e altri tipi di dati

Implementazione

Internamente utilizza un trucco molto accurato:

@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 restituisce la stringa vuota solo la prima volta che viene richiamata, quindi restituisce il separatore:

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

Risposta fissa Rob Dickerson.

È più facile da usare:

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

Invece di usare la concatenazione di stringhe, dovresti usare StringBuilder se il tuo codice non è threadato e StringBuffer se lo è.

0
Aaron

Quindi un paio di cose che potresti fare per avere l'impressione che sembra che tu stia cercando:

1) Estendi la classe Elenco e aggiungi ad essa il metodo di join. Il metodo di join farebbe semplicemente il lavoro di concatenazione e aggiunta del delimitatore (che potrebbe essere un parametro per il metodo join)

2) Sembra che Java 7 stia aggiungendo metodi di estensione a Java, il che ti consente semplicemente di collegare un metodo specifico a una classe: in questo modo potresti scrivere quel metodo di join e aggiungerlo come un metodo di estensione a List o anche a Collezione.

La soluzione 1 è probabilmente l'unica realistica, ora, anche se Java 7 non è ancora uscito :) Ma dovrebbe funzionare bene.

Per utilizzare entrambi, devi semplicemente aggiungere tutti gli elementi all'Elenco o alla Raccolta come al solito, quindi chiamare il nuovo metodo personalizzato per "aggiungerli".

0
user13276

Lo stai rendendo un po 'più complicato di quanto non debba essere. Iniziamo con la fine del tuo esempio:

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

Con la piccola modifica di utilizzare un StringBuilder invece di una stringa, questo diventa:

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

Quando hai finito (presumo che devi controllare anche qualche altra condizione), assicurati di rimuovere la virgola di coda con un comando come questo:

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

E infine, prendi la stringa che desideri

parameterString.toString();

È anche possibile sostituire "," nella seconda chiamata da aggiungere con una stringa delimitatore generica che può essere impostata su qualsiasi valore. Se hai un elenco di cose che sai che devi aggiungere (non condizionatamente), puoi inserire questo codice in un metodo che prende un elenco di stringhe.

0
cjw2600

Leggero miglioramento [velocità] della versione di 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

Personalmente uso abbastanza spesso la seguente semplice soluzione per la registrazione:

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

Puoi provare qualcosa come questo:

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