A maneira mais simples de delimitar uma lista por vírgulas?


104

Qual é a maneira mais clara de delimitar uma lista por vírgulas em Java?

Eu conheço várias maneiras de fazer isso, mas estou me perguntando qual é a melhor maneira (onde "melhor" significa mais claro e / ou mais curto, não o mais eficiente.

Tenho uma lista e quero fazer um loop, imprimindo cada valor. Quero imprimir uma vírgula entre cada item, mas não depois do último (nem antes do primeiro).

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

Solução de amostra 1:

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

Solução de amostra 2 - crie uma sublista:

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

Solução de amostra 3:

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

Eles tratam o primeiro item de maneira especial; em vez disso, pode-se tratar o último especialmente.

Aliás, aqui está como List toString é implementado (é herdado de AbstractCollection), em 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(", ");
    }
}

Ele sai do loop mais cedo para evitar a vírgula após o último item. BTW: esta é a primeira vez que me lembro de ter visto "(esta coleção)"; aqui está o código para provocá-lo:

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

Congratulo-me com qualquer solução, mesmo que use bibliotecas inesperadas (regexp?); e também soluções em outras linguagens que não Java (por exemplo, eu acho que Python / Ruby tem uma função intercalar - como isso é implementado?).

Esclarecimento : por bibliotecas, quero dizer as bibliotecas Java padrão. Para outras bibliotecas, considero-as com outras linguagens, e tenho interesse em saber como estão implementadas.

O kit de ferramentas EDIT mencionou uma questão semelhante: Última iteração do loop for aprimorado em java

E outro: o último elemento em um loop merece um tratamento separado?


Respostas:


226

Java 8 e posterior

Usando a StringJoinerclasse:

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

Usando Stream, e Collectors:

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

Java 7 e anterior

Veja também # 285523

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

1
Uma maneira muito surpreendente de armazenar estado! É quase OO polimórfico. Não gosto da atribuição desnecessária a cada loop, mas aposto que é mais eficiente do que um if. A maioria das soluções repete um teste que sabemos não ser verdadeiro - embora ineficientes, são provavelmente as mais claras. Obrigado pelo link!
13ren

1
Eu gosto disso, mas por ser inteligente e surpreendente, achei que não seria apropriado usar (surpreender não é bom!). No entanto, eu não pude evitar. Ainda não tenho certeza sobre isso; no entanto, é tão curto que é fácil de descobrir, desde que haja um comentário para lhe dar uma dica.
13ren

6
@ 13ren: Você e o tio Bob precisam conversar sobre um código "inteligente e surpreendente".
Stefan Kendall

Eu sinto que perdi anos sem saber disso ^^;
Lago de

Por que o Java precisa transformar essa tarefa em um código feio?
Eduardo

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

1
Fonte encontrada: svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/… O método de junção tem 9 sobrecargas. Os baseados em iteração são como "Solução de amostra 3"; os baseados em índice usam: if (i> startIndex) {<adicionar separador>}
13ren

No entanto, estou realmente atrás de implementações. É uma boa ideia envolvê-lo em uma função, mas, na prática, meu delimitador às vezes é uma nova linha e cada linha também é recuada para alguma profundidade especificada. A menos que ... join (list, "\ n" + indent) ... isso sempre funcionará? Desculpe, apenas pensando em voz alta.
13ren

2
Na minha opinião, esta é a solução mais bonita e curta
Jesper Rønn-Jensen

provavelmente, list.iterator ()?
Vanuan

32

Java 8 oferece várias novas maneiras de fazer isso:

Exemplo:

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

A abordagem usando fluxos assume import static java.util.stream.Collectors.joining;.


Este é IMHO, se você estiver usando java 8, a melhor resposta.
scravy


7

Se você usar o Spring Framework, poderá fazê-lo com 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)

Obrigado, mas como isso é implementado?
13ren

6

Com base na implementação List toString do Java:

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

Ele usa uma gramática como esta:

List --> (Item , )* Item

Por ser baseado no último em vez de no primeiro, ele pode verificar a vírgula de salto com o mesmo teste para verificar o fim da lista. Eu acho que este é muito elegante, mas não tenho certeza sobre clareza.


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

4

Há uma maneira bonita de fazer isso usando Java 8:

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

TextUtils.join (",", lista);
Arul Pandian de

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

Sim, é um pouco enigmático, mas uma alternativa ao que já foi postado.
cherouvim,

1
Acho que o código parece bom, IMHO é mais bonito do que sua outra resposta e dificilmente é 'enigmático'. Enrole em um método chamado Junte-se e sua risada, ainda assim eu esperaria que a linguagem tivesse uma maneira melhor de resolver o que é realmente um problema comum.
Tarn

@tarn: você acha que parece bom porque tem experiência em Python / perl;) Eu também gosto
cherouvim,

3

Uma opção para o loop 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());
}

Atualização : Como Dave Webb mencionou nos comentários, isso pode não produzir resultados corretos se os primeiros itens da lista forem strings vazias.


meh .. ainda tem if no loop.
sfossen,

Você pode usar um loop foreach para torná-lo mais curto.
Peter Lawrey,

Isso não funcionará se o primeiro item da lista for uma string vazia.
Dave Webb

@Dave Webb: Sim, obrigado. A questão não tem tal requisito.
cherouvim

@cherovium: A questão não diz que nenhum dos itens será a string vazia, então é um requisito implícito. Se você estiver criando um arquivo CSV, os campos vazios podem ser muito comuns.
Dave Webb

2

Eu normalmente faço isso:

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

Embora eu ache que vou fazer essa verificação de agora em diante de acordo com a implementação Java;)


Essa é a mesma lógica do exemplo 3, gosto porque não faz nada desnecessário. No entanto, não sei se é o mais claro. BTW: é um pouco mais eficiente se você colocar o while dentro do if, evitando dois testes quando a lista está vazia. Isso também torna isso um pouco mais claro para mim.
13ren,

Pelo amor de Deus, use um StringBuilder
scravy de

2

Se você pode usar Groovy (que é executado no JVM):

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

obrigado, mas estou interessado em implementações. Como este é implementado?
13ren

2

(Copie e cole minha própria resposta daqui .) Muitas das soluções descritas aqui são um pouco exageradas, IMHO, especialmente aquelas que dependem de bibliotecas externas. Há um idioma limpo e claro para obter uma lista separada por vírgulas que sempre usei. Ele depende do operador condicional (?):

Editar : solução original correta, mas não ideal de acordo com os comentários. Tentando uma segunda vez:

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

Aí está, em 4 linhas de código, incluindo a declaração da matriz e o StringBuilder.

2ª Edição : Se você estiver lidando com um Iterador:

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

Este e stackoverflow.com/questions/668952/… acima são semelhantes. É curto e claro, mas se você tiver apenas um iterador, primeiro precisará converter. Ineficiente, mas vale a pena pela clareza dessa abordagem?
13ren

Ugh, concatenação de string criando um StringBuilder temporário dentro do uso de um StringBuilder. Ineficiente. Melhor (mais parecido com Java, mas melhor executando código) seria "if (i> 0) {builder.append (',');}" seguido por builder.append (array [i]);
Eddie

Sim, se você olhar no bytecode, você está certo. Não posso acreditar que uma pergunta tão simples possa ficar tão complicada. Gostaria de saber se o compilador pode otimizar aqui.
Julien Chastang,

Forneceu a segunda solução, esperançosamente, melhor.
Julien Chastang,

Pode ser melhor dividi-los (para que as pessoas possam votar em um ou outro).
13ren

1

Eu costumo usar algo parecido com a versão 3. Funciona bem c / c ++ / bash / ...: P


1

Não o compilei ... mas deve funcionar (ou estar perto de funcionar).

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

Eu gosto desse jeito, só prefiro outra formatação. Mas para mencionar uma pequena correção: se a lista estiver vazia, isso lançará uma exceção IndexOutOfBounds. Portanto, faça uma verificação antes de setLength ou use Math.max (0, ...)
tb-

1

Isso é muito curto, muito claro, mas dá à minha sensibilidade os horrores arrepiantes. Também é um pouco estranho se adaptar a diferentes delimitadores, especialmente se for uma String (não char).

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

Inspirado por: Última iteração do loop for aprimorado em java


1
StringBuffer sb = new StringBuffer();

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

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

Acho que este é muito fácil de incorporar a uma classe de utilitário estático, ao mesmo tempo que é muito fácil de ler e entender. Ele também não requer nenhuma variável ou estado extra além da própria lista, o loop e o delimitador.
Joachim H. Skeie,

Isso fornecerá algo como:Item1Item2, Item3, Item4,

Pelo amor de Deus, use um StringBuilder
scravy de

1

Gosto um pouco dessa abordagem, que encontrei em um blog há algum tempo. Infelizmente não me lembro do nome / URL do blog.

Você pode criar uma classe de utilitário / auxiliar semelhante a esta:

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

Usar a classe auxiliar é simples assim:

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

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

Apenas uma nota: deve ser "delimitador", não "delimitador".
Michael Myers

1

Como seu delimitador é "," você pode usar qualquer um dos seguintes:

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 o seu delimitador for QUALQUER OUTRA coisa, isso não vai funcionar para você.


1

Gosto desta solução:

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

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

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

Suponho que também possa usar StringBuilder.


1

Na minha opinião, este é o mais simples de ler e entender:

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

0

Em Python é fácil

",". junte-se (sua lista)

Em C #, há um método estático na classe String

String.Join (",", sua lista de strings)

Desculpe, não tenho certeza sobre o Java, mas achei melhor falar quando você perguntou sobre outras linguagens. Tenho certeza de que haveria algo semelhante em Java.


IIRC, você obtém um trailing, na versão .Net. :-(

1
Acabei de testar, sem delimitador final em .NET ou Python
tarn

1
obrigado! Eu encontrei a fonte: svn.python.org/view/python/branches/release22-branch/Objects/… procure por "Catenate tudo". Omite o separador do último item.
13ren

@ 13ren Muito bem! Ajuda? Tive um bloqueio e rapidamente me lembrei por que escolhi não programar em C atualmente: P
tarn,

@tarn, sim, é um exemplo interessante porque só adiciona o comman se não for o último item: if (i <seqlen - 1) {<adicionar separador>}
13ren

0

Você também pode adicionar incondicionalmente a string delimitadora e, após o loop, remover o delimitador extra no final. Então, um "se a lista está vazia, então retorne esta string" no início permitirá que você evite a verificação no final (já que você não pode remover caracteres de uma lista vazia)

Portanto, a questão realmente é:

"Dados um loop e um if, qual você acha que é a maneira mais clara de tê-los juntos?"


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
Acho que você quis dizerlistToString.substring(0, listToString.length()-separator.length());
13ren

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

Com base na maneira mais clara de delimitar uma lista por vírgulas (Java)?

Usando esta ideia: O último elemento em um loop merece um tratamento separado?


1
Para que isso funcione, você precisa saber que há pelo menos 1 elemento na lista, caso contrário, seu primeiro loop for travará com uma IndexOutOfBoundsException.
Joachim H. Skeie,

@Joachim obrigado, você está certo, é claro. Que solução horrível eu escrevi! Eu vou mudar for (int i=0; i<1; i++)para if (array.length>0).
13ren 10/10/11

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
Acabei de notar que é semelhante à resposta que TofuBeer deu
Eblis

0

Nenhuma das respostas usa recursão até agora ...

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

Método

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

Dada a matriz de [1,2,3]

join(myArray, ",") // 1,2,3
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.