Il modo più semplice per delimitare un elenco con virgole?


104

Qual è il modo più chiaro per delimitare un elenco in Java?

Conosco diversi modi per farlo, ma mi chiedo quale sia il modo migliore (dove "migliore" significa più chiaro e / o più breve, non il più efficiente.

Ho una lista e voglio scorrerla in loop, stampando ogni valore. Voglio stampare una virgola tra ogni elemento, ma non dopo l'ultimo (né prima del primo).

List --> Item ( , Item ) *
List --> ( Item , ) * Item

Soluzione campione 1:

boolean isFirst = true;
for (Item i : list) {
  if (isFirst) {
    System.out.print(i);        // no comma
    isFirst = false;
  } else {
    System.out.print(", "+i);   // comma
  }
}

Soluzione di esempio 2: crea una sottolista:

if (list.size()>0) {
  System.out.print(list.get(0));   // no comma
  List theRest = list.subList(1, list.size());
  for (Item i : theRest) {
    System.out.print(", "+i);   // comma
  }
}

Soluzione campione 3:

  Iterator<Item> i = list.iterator();
  if (i.hasNext()) {
    System.out.print(i.next());
    while (i.hasNext())
      System.out.print(", "+i.next());
  }

Questi trattano specialmente il primo oggetto; si potrebbe invece trattare in modo speciale l'ultimo.

Per inciso, ecco come List toString viene implementato (è ereditato da AbstractCollection), in Java 1.6:

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

Esce dal ciclo in anticipo per evitare la virgola dopo l'ultimo elemento. BTW: questa è la prima volta che ricordo di aver visto "(questa collezione)"; ecco il codice per provocarlo:

List l = new LinkedList();
l.add(l);
System.out.println(l);

Accolgo con favore qualsiasi soluzione, anche se utilizzano librerie impreviste (regexp?); e anche soluzioni in lingue diverse (ad esempio Java Credo Python / Rubino hanno una intervallare funzione - come è che ? implementata).

Chiarimento : per librerie, intendo le librerie Java standard. Per altre biblioteche, le considero con altri linguaggi e sono interessato a sapere come vengono implementate.

Il toolkit EDIT ha menzionato una domanda simile: l' ultima iterazione del ciclo for migliorato in java

E un altro: l'ultimo elemento di un ciclo merita un trattamento separato?


Risposte:


226

Java 8 e versioni successive

Utilizzando la StringJoinerclasse:

StringJoiner joiner = new StringJoiner(",");
for (Item item : list) {
    joiner.add(item.toString());
}
return joiner.toString();

Utilizzando Stream, e Collectors:

return list.stream().
       map(Object::toString).
       collect(Collectors.joining(",")).toString();

Java 7 e versioni precedenti

Vedi anche # 285523

String delim = "";
for (Item i : list) {
    sb.append(delim).append(i);
    delim = ",";
}

1
Un modo molto sorprendente per memorizzare lo stato! È quasi OO polimorfico. Non mi piace l'assegnazione non necessaria ad ogni ciclo, ma scommetto che è più efficiente di un if. La maggior parte delle soluzioni ripete un test che sappiamo non sarà vero, sebbene inefficienti, sono probabilmente le più chiare. Grazie per il collegamento!
13ren

1
Mi piace, ma poiché è intelligente e sorprendente, ho pensato che non sarebbe stato appropriato da usare (sorprendere non è buono!). Tuttavia, non ho potuto trattenermi. Non ne sono ancora sicuro; tuttavia, è così breve che è facile da risolvere a condizione che ci sia un commento per informarti.
13ren

6
@ 13ren: Tu e lo zio Bob dovete parlare di un codice "intelligente e sorprendente".
Stefan Kendall

Mi sento come se avessi sprecato anni senza saperlo ^^;
Lago

Perché Java ha bisogno di trasformare tale compito in un brutto codice?
Eduardo

82
org.apache.commons.lang3.StringUtils.join(list,",");

1
Trovato la fonte: svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/… Il metodo join ha 9 sovraccarichi. Quelli basati sull'iterazione sono come "Soluzione campione 3"; quelli basati sull'indice usano: if (i> startIndex) {<add separator>}
13ren

Tuttavia, sto davvero cercando le implementazioni. È una buona idea racchiuderlo in una funzione, ma in pratica, il mio delimitatore a volte è una nuova riga e ogni riga è anche rientrata a una profondità specificata. A meno che ... join (list, "\ n" + indent) ... funzionerà sempre? Scusa, sto solo pensando ad alta voce.
13ren

2
Secondo me, questa è la soluzione più bella e più breve
Jesper Rønn-Jensen

probabilmente, list.iterator ()?
Vanuan

32

Java 8 offre diversi nuovi modi per farlo:

Esempio:

// Util method for strings and other char sequences
List<String> strs = Arrays.asList("1", "2", "3");
String listStr1 = String.join(",", strs);

// For any type using streams and collectors
List<Object> objs = Arrays.asList(1, 2, 3);
String listStr2 = objs.stream()
    .map(Object::toString)
    .collect(joining(",", "[", "]"));

// Using the new StringJoiner class
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.setEmptyValue("<empty>");
for (Integer i : objs) {
  joiner.add(i.toString());
}
String listStr3 = joiner.toString();

L'approccio che utilizza i flussi presuppone import static java.util.stream.Collectors.joining;.


Questo è IMHO, se stai usando java 8, la risposta migliore.
scabroso


7

Se usi Spring Framework puoi farlo con StringUtils :

public static String arrayToDelimitedString(Object[] arr)
public static String arrayToDelimitedString(Object[] arr, String delim)
public static String collectionToCommaDelimitedString(Collection coll)
public static String collectionToCommaDelimitedString(Collection coll, String delim)

Grazie, ma come viene implementato?
13ren

6

Basato sull'implementazione List toString di Java:

Iterator i = list.iterator();
for (;;) {
  sb.append(i.next());
  if (! i.hasNext()) break;
  ab.append(", ");
}

Usa una grammatica come questa:

List --> (Item , )* Item

Essendo basato sull'ultimo invece che sul primo, può verificare la presenza di virgola con lo stesso test per verificare la fine dell'elenco. Penso che questo sia molto elegante, ma non sono sicuro della chiarezza.


4
String delimiter = ",";
StringBuilder sb = new StringBuilder();
for (Item i : list) {
    sb.append(delimiter).append(i);
}
sb.toString().replaceFirst(delimiter, "");

4

C'è un bel modo per ottenere questo risultato usando Java 8:

List<String> list = Arrays.asList(array);
String joinedString = String.join(",", list);

TextUtils.join (",", list);
Arul Pandian

3
for(int i=0, length=list.size(); i<length; i++)
  result+=(i==0?"":", ") + list.get(i);

Sì, è un po 'criptico, ma un'alternativa a ciò che è già stato pubblicato.
cherouvim

1
Penso che il codice sembri a posto, IMHO è più carina dell'altra tua risposta e non è affatto "criptica". Avvolgilo in un metodo chiamato Join e le tue risate, tuttavia mi aspetto che la lingua abbia un modo migliore per risolvere quello che è davvero un problema comune.
tarn

@tarn: pensi che stia bene perché hai uno sfondo Python / Perl;) Piace anche a
me

3

Un'opzione per il ciclo foreach è:

StringBuilder sb = new StringBuilder();
for(String s:list){
  if (sb.length()>0) sb.append(",");
  sb.append(s);
}

2
StringBuffer result = new StringBuffer();
for(Iterator it=list.iterator; it.hasNext(); ) {
  if (result.length()>0)
    result.append(", ");
  result.append(it.next());
}

Aggiornamento : come menzionato da Dave Webb nei commenti, ciò potrebbe non produrre risultati corretti se i primi elementi nell'elenco sono stringhe vuote.


meh .. ha ancora il se nel ciclo.
sfossen

Puoi usare un ciclo foreach per renderlo più breve.
Peter Lawrey

Ciò non funzionerà se il primo elemento nell'elenco è una stringa vuota.
Dave Webb

@ Dave Webb: Sì, grazie. La domanda però non ha tale requisito.
cherouvim

@cherovium: la domanda non dice che nessuno degli elementi sarà la stringa vuota, quindi è un requisito implicito. Se stai creando un file CSV, i campi vuoti possono essere piuttosto comuni.
Dave Webb

2

Di solito lo faccio:

StringBuffer sb = new StringBuffer();
Iterator it = myList.iterator();
if (it.hasNext()) { sb.append(it.next().toString()); }
while (it.hasNext()) { sb.append(",").append(it.next().toString()); }

Anche se penso che farò questo controllo d'ora in poi come per l'implementazione Java;)


È la stessa logica del campione 3, mi piace perché non fa nulla di inutile. Tuttavia, non so se sia il più chiaro. BTW: è leggermente più efficiente se metti il ​​while all'interno dell'if, evitando due test quando l'elenco è vuoto. Lo rende anche leggermente più chiaro, per me.
13ren

Per l'amor del cielo, usa un StringBuilder
scravy

2

Se puoi usare Groovy (che gira su JVM):

def list = ['a', 'b', 'c', 'd']
println list.join(',')

grazie, ma sono interessato alle implementazioni. Come viene implementato questo?
13ren

2

(Copia incolla della mia risposta da qui .) Molte delle soluzioni descritte qui sono un po 'esagerate, IMHO, specialmente quelle che si basano su librerie esterne. C'è un bel linguaggio pulito e chiaro per ottenere un elenco separato da virgole che ho sempre usato. Si basa sull'operatore condizionale (?):

Modifica : soluzione originale corretta, ma non ottimale in base ai commenti. Provando una seconda volta:

int[] array = {1, 2, 3};
StringBuilder builder = new StringBuilder();
for (int i = 0 ;  i < array.length; i++)
       builder.append(i == 0 ? "" : ",").append(array[i]);

Ecco fatto, in 4 righe di codice inclusa la dichiarazione dell'array e lo StringBuilder.

2a modifica : se hai a che fare con un iteratore:

    List<Integer> list = Arrays.asList(1, 2, 3);
    StringBuilder builder = new StringBuilder();
    for (Iterator it = list.iterator(); it.hasNext();)
        builder.append(it.next()).append(it.hasNext() ? "," : "");

Questo e stackoverflow.com/questions/668952/… sopra sono simili. È breve e chiaro, ma se hai solo un iteratore, devi prima convertire. Inefficiente, ma forse ne vale la pena per la chiarezza di questo approccio?
13ren

Ugh, concatenazione di stringhe che crea uno StringBuilder temporaneo all'interno dell'uso di uno StringBuilder. Inefficiente. Meglio (più simile a Java, ma codice in esecuzione migliore) sarebbe "if (i> 0) {builder.append (',');}" seguito da builder.append (array [i]);
Eddie

Sì, se guardi nel bytecode, hai ragione. Non posso credere che una domanda così semplice possa diventare così complicata. Mi chiedo se il compilatore possa ottimizzare qui.
Julien Chastang

Fornito una seconda soluzione, si spera migliore.
Julien Chastang

Potrebbe essere meglio dividerli (così le persone possono votare l'uno o l'altro).
13ren

1

Di solito uso qualcosa di simile alla versione 3. Funziona bene c / c ++ / bash / ...: P


1

Non l'ho compilato ... ma dovrebbe funzionare (o essere vicino a funzionare).

public static <T> String toString(final List<T> list, 
                                  final String delim)
{
    final StringBuilder builder;

    builder = new StringBuilder();

    for(final T item : list)
    {
        builder.append(item);
        builder.append(delim);
    }

    // kill the last delim
    builder.setLength(builder.length() - delim.length());

    return (builder.toString());
}

Mi piace in questo modo, preferirei solo un'altra formattazione. Ma per menzionare una piccola correzione: se l'elenco è vuoto, verrà generata un'eccezione IndexOutOfBounds. Quindi, fai un controllo prima di setLength o usa Math.max (0, ...)
tb-

1

Questo è molto breve, molto chiaro, ma dà alla mia sensibilità gli orrori striscianti. È anche un po 'scomodo adattarsi a diversi delimitatori, specialmente se una stringa (non char).

for (Item i : list)
  sb.append(',').append(i);
if (sb.charAt(0)==',') sb.deleteCharAt(0);

Ispirato da: ultima iterazione del ciclo for migliorato in java


1
StringBuffer sb = new StringBuffer();

for (int i = 0; i < myList.size(); i++)
{ 
    if (i > 0) 
    {
        sb.append(", ");
    }

    sb.append(myList.get(i)); 
}

Trovo che questo sia molto facile da incorporare in una classe di utilità statica, mentre è anche molto facile da leggere e capire. Inoltre, non richiede variabili o stati aggiuntivi oltre all'elenco stesso, al ciclo e al delimitatore.
Joachim H. Skeie

Ciò fornirà qualcosa del tipo:Item1Item2, Item3, Item4,

Per l'amor del cielo, usa un StringBuilder
scravy

1

Mi piace un po 'questo approccio, che ho trovato su un blog qualche tempo fa. Purtroppo non ricordo il nome / URL del blog.

Puoi creare una classe di utilità / aiuto simile a questa:

private class Delimiter
{
    private final String delimiter;
    private boolean first = true;

    public Delimiter(String delimiter)
    {
        this.delimiter = delimiter;
    }

    @Override
    public String toString()
    {
        if (first) {
            first = false;
            return "";
        }

        return delimiter;
    }
}

Usare la classe helper è semplice come questo:

StringBuilder sb = new StringBuilder();
Delimiter delimiter = new Delimiter(", ");

for (String item : list) {
    sb.append(delimiter);
    sb.append(item);
}

Solo una nota: dovrebbe essere "delimitatore", non "delimetro".
Michael Myers

1

Poiché il delimitatore è "," potresti utilizzare uno dei seguenti:

public class StringDelim {

    public static void removeBrackets(String string) {
        System.out.println(string.substring(1, string.length() - 1));
    }

    public static void main(String... args) {
        // Array.toString() example
        String [] arr = {"Hi" , "My", "name", "is", "br3nt"};
        String string = Arrays.toString(arr);
        removeBrackets(string);

        // List#toString() example
        List<String> list = new ArrayList<String>();
        list.add("Hi");
        list.add("My");
        list.add("name");
        list.add("is");
        list.add("br3nt");
        string = list.toString();
        removeBrackets(string);

        // Map#values().toString() example
        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("1", "Hi");
        map.put("2", "My");
        map.put("3", "name");
        map.put("4", "is");
        map.put("5", "br3nt");
        System.out.println(map.values().toString());
        removeBrackets(string);

        // Enum#toString() example
        EnumSet<Days> set = EnumSet.allOf(Days.class);
        string = set.toString();
        removeBrackets(string);
    }

    public enum Days {
        MON("Monday"),
        TUE("Tuesday"),
        WED("Wednesday"),
        THU("Thursday"),
        FRI("Friday"),
        SAT("Saturday"),
        SUN("Sunday");

        private final String day;

        Days(String day) {this.day = day;}
        public String toString() {return this.day;}
    }
}

Se il tuo delimitatore è QUALCOSA, allora questo non funzionerà per te.


1

Mi piace questa soluzione:

String delim = " - ", string = "";

for (String item : myCollection)
    string += delim + item;

string = string.substring(delim.length());

Presumo che possa fare uso anche di StringBuilder.


1

Secondo me, questo è il più semplice da leggere e capire:

StringBuilder sb = new StringBuilder();
for(String string : strings) {
    sb.append(string).append(',');
}
sb.setLength(sb.length() - 1);
String result = sb.toString();

0

In Python è facile

",". iscriviti (la tua lista)

In C # esiste un metodo statico sulla classe String

String.Join (",", yourlistofstrings)

Mi spiace, non sono sicuro di Java, ma ho pensato di rispondere quando hai chiesto informazioni su altre lingue. Sono sicuro che ci sarebbe qualcosa di simile in Java.


IIRC, ottieni un finale, nella versione .Net. :-(

1
Appena testato, nessun delimitatore finale in .NET o Python
tarn

1
Grazie! Ho trovato la fonte: svn.python.org/view/python/branches/release22-branch/Objects/… cerca "Catenate everything". Omette il separatore per l'ultimo elemento.
13ren

@ 13ren Ben fatto! Aiuta? Avevo un lucchetto e mi sono subito ricordato perché ho scelto di non programmare in C in questi giorni: P
tarn

@tarn, sì, è un esempio interessante perché aggiunge il comando solo se non è l'ultimo elemento: if (i <seqlen - 1) {<add separator>}
13ren

0

Puoi anche aggiungere incondizionatamente la stringa delimitatore e dopo il ciclo rimuovere il delimitatore extra alla fine. Quindi un "se l'elencoèvuoto restituisci questa stringa" all'inizio ti permetterà di evitare il controllo alla fine (dato che non puoi rimuovere caratteri da un elenco vuoto)

Quindi la domanda è davvero:

"Dato un ciclo e un se, quale pensi sia il modo più chiaro per averli insieme?"


0
public static String join (List<String> list, String separator) {
  String listToString = "";

  if (list == null || list.isEmpty()) {
   return listToString;
  }

  for (String element : list) {
   listToString += element + separator;
  }

  listToString = listToString.substring(0, separator.length());

  return listToString;
}

1
Penso che listToString.substring(0, listToString.length()-separator.length());
volessi

0
if (array.length>0)          // edited in response Joachim's comment
  sb.append(array[i]);
for (int i=1; i<array.length; i++)
  sb.append(",").append(array[i]);

Basato sul modo più chiaro per delimitare un elenco con virgole (Java)?

Usando questa idea: l'ultimo elemento di un ciclo merita un trattamento separato?


1
Perché funzioni, devi sapere che c'è almeno 1 elemento nell'elenco, altrimenti il ​​tuo primo ciclo for si bloccherà con un'eccezione IndexOutOfBoundsException.
Joachim H. Skeie

@Joachim grazie, hai ragione ovviamente. Che brutta soluzione ho scritto! Cambierò for (int i=0; i<1; i++)in if (array.length>0).
13ren

0
public String toString(List<Item> items)
{
    StringBuilder sb = new StringBuilder("[");

    for (Item item : items)
    {
        sb.append(item).append(", ");
    }

    if (sb.length() >= 2)
    {
        //looks cleaner in C# sb.Length -= 2;
        sb.setLength(sb.length() - 2);
    }

    sb.append("]");

    return sb.toString();
}

1
Ho appena notato che è simile alla risposta data da TofuBeer
Eblis

0

Nessuna delle risposte utilizza la ricorsione finora ...

public class Main {

    public static String toString(List<String> list, char d) {
        int n = list.size();
        if(n==0) return "";
        return n > 1 ? Main.toString(list.subList(0, n - 1), d) + d
                  + list.get(n - 1) : list.get(0);
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList(new String[]{"1","2","3"});
        System.out.println(Main.toString(list, ','));
    }

}

0
private String betweenComma(ArrayList<String> strings) {
    String united = "";
    for (String string : strings) {
        united = united + "," + string;
    }
    return united.replaceFirst(",", "");
}

-1

Metodo

String join(List<Object> collection, String delimiter){
    StringBuilder stringBuilder = new StringBuilder();
    int size = collection.size();
    for (Object value : collection) {
        size --;
        if(size > 0){
            stringBuilder.append(value).append(delimiter);
        }
    }

    return stringBuilder.toString();
}

uso

Dato l'array di [1,2,3]

join(myArray, ",") // 1,2,3
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.