Usando o Java 8 para converter uma lista de objetos em uma sequência obtida a partir do método toString ()


206

Existem muitas coisas novas úteis no Java 8. Por exemplo, eu posso iterar com um fluxo sobre uma lista de objetos e depois somar os valores de um campo específico das Objectinstâncias da. Por exemplo

public class AClass {
  private int value;
  public int getValue() { return value; }
}

Integer sum = list.stream().mapToInt(AClass::getValue).sum();

Portanto, estou perguntando se existe alguma maneira de criar um Stringque concatene a saída do toString()método a partir das instâncias em uma única linha.

List<Integer> list = ...

String concatenated = list.stream().... //concatenate here with toString() method from java.lang.Integer class

Suponha que listcontém números inteiros 1, 2e 3, eu espero que concatenatedseja "123"ou "1,2,3".


Respostas:


369

Uma maneira simples é anexar os itens da lista em um StringBuilder

   List<Integer> list = new ArrayList<>();
   list.add(1);
   list.add(2);
   list.add(3);

   StringBuilder b = new StringBuilder();
   list.forEach(b::append);

   System.out.println(b);

você também pode tentar:

String s = list.stream().map(e -> e.toString()).reduce("", String::concat);

Explicação: map converte fluxo Inteiro em fluxo String, depois é reduzido como concatenação de todos os elementos.

Nota: É o normal reductionque executa em O (n 2 )

para um melhor desempenho, use um StringBuilderou mutable reductionsemelhante à resposta de F. Böller.

String s = list.stream().map(Object::toString).collect(Collectors.joining(","));

Ref: Redução de Fluxo


2
Sim, não uma solução em linha, mas uma solução.
mat_boy

não entendi isso. O que você está esperando?
Shail016

2
Eu não entendo por que "redução normal que executa em O (n2)". para mim que "parece" mais como O (n) ...
datahaki

2
@datahaki Eu não sou um cara Java, mas acho que a concatenação (redução) de string iterativa requer (re) alocação de array em cada iteração, o que o torna O (n ^ 2). joindo outro lado, pré-alocaria uma única matriz grande o suficiente para armazenar a sequência final antes de preenchê-la, tornando-a O (n).
Eli Korvigo 18/01/19

2
Dica muito boa. Talvez você possa simplificar usando a notação "::". Então list.stream().map(Object::toString).reduce("", String::concat),. Usar map e-> e.toString()é um pouco redundante.
E2A

194

Há um coletor joiningna API. É um método estático no Collectors.

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

Não é perfeito por causa da chamada necessária de toString, mas funciona. Delimitadores diferentes são possíveis.


7
Para ser completo: para que o código exato funcione, você precisaimport static java.util.stream.Collectors.joining;
Mahdi

7
Por que precisamos do mapeamento e do explícito `toString '? Se o coletor está esperando elementos String, a conversão deve ser chamada implicitamente, não ?!
Basel Shishani

10

Caso alguém esteja tentando fazer isso sem o java 8, existe um truque muito bom. List.toString () já retorna uma coleção parecida com esta:

[1,2,3]

Dependendo de seus requisitos específicos, isso pode ser pós-processado para o que você quiser, desde que os itens da lista não contenham [] ou,.

Por exemplo:

list.toString().replace("[","").replace("]","") 

ou se seus dados podem conter colchetes, isso:

String s=list.toString();
s = s.substring(1,s.length()-1) 

você obterá uma saída bastante razoável.

Um item de matriz em cada linha pode ser criado assim:

list.toString().replace("[","").replace("]","").replaceAll(",","\r\n")

Eu usei essa técnica para criar dicas de ferramentas html a partir de uma lista em um aplicativo pequeno, com algo como:

list.toString().replace("[","<html>").replace("]","</html>").replaceAll(",","<br>")

Se você tiver uma matriz, inicie com Arrays.asList (list) .toString ()

Eu sou totalmente responsável pelo fato de que isso não é o ideal, mas não é tão ineficiente quanto você imagina e é bastante simples de ler e entender. É, no entanto, bastante inflexível - em particular, não tente separar os elementos com replaceAll se seus dados puderem conter vírgulas e usar a versão de substring se você tiver colchetes nos dados, mas para uma matriz de números é praticamente perfeito.


Embora isso normalmente funcione, não é garantido que - Listnão impõe a estrutura de toString(). AbstractCollectionno entanto, usa essa estrutura por padrão, e acho que todas as Listimplementações de uso geral no Java SE também. Como exemplo de um que org.springframework.util.AutoPopulatingListnão implementa , o Spring não implementa toString()e, portanto, retornaria, por exemplo, " org.springframework.util.AutoPopulatingList@170a1".
M. Justin

Arrays.toString()seria uma escolha melhor do que Arrays.asList(list).toString(), como é definido para retornar uma string equivalente, é mais conciso e não requer a criação de objetos adicionais.
M. Justin

@ M.Justin Não é um ponto ruim para a parte da matriz desta resposta, mas e uma "lista" simples? Você realmente não pode usar Arrays.toString () nisso. Como você sugeriria usar essa técnica em algo como o AutoPopulatingList que você mencionou? Talvez: new ArrayList (autoPopulatingList) .toString ()? Ou eu acho que você pode convertê-lo em uma matriz. Talvez se você correu para tal problema a você deve espero que você esteja em 1.8 ou posterior e pode usar uma das outras respostas neste segmento ...
Bill K

5

As outras respostas estão bem. No entanto, você também pode passar Collectors.toList () como parâmetro para Stream.collect () para retornar os elementos como um ArrayList.

System.out.println( list.stream().map( e -> e.toString() ).collect( toList() ) );

Se você usar System.out.print(ln), (list)ele usará o toString()método dos elementos para imprimir a lista. Portanto, esse trecho de código está apenas repetindo o que acontece dentro toStringda lista.
Shirkam

1
Deve ser Collectors.toList (), a menos que você esteja importando estática.
precisa saber é o seguinte

Sim ... eu importei estática para o exemplo. Eu disse, "Collectors.toList ()" no corpo da resposta ...;)
Eddie B

2

StringListName = ObjectListName.stream (). Map (m -> m.toString ()) .collect (Collectors.toList ());


2

Existe um método na API String para os casos de "lista de junção de cadeias", você nem precisa do Stream.

List<String> myStringIterable = Arrays.asList("baguette", "bonjour");

String myReducedString = String.join(",", myStringIterable);

// And here you obtain "baguette,bonjour" in your myReducedString variable

1
List<String> list = Arrays.asList("One", "Two", "Three");
    list.stream()
            .reduce("", org.apache.commons.lang3.StringUtils::join);

Ou

List<String> list = Arrays.asList("One", "Two", "Three");
        list.stream()
                .reduce("", (s1,s2)->s1+s2);

Essa abordagem também permite criar um resultado de sequência a partir de uma lista de objetos

List<Wrapper> list = Arrays.asList(w1, w2, w2);
        list.stream()
                .map(w->w.getStringValue)
                .reduce("", org.apache.commons.lang3.StringUtils::join);

Aqui, a função reduzir permite que você tenha algum valor inicial ao qual deseja acrescentar uma nova string Exemplo:

 List<String> errors = Arrays.asList("er1", "er2", "er3");
            list.stream()
                    .reduce("Found next errors:", (s1,s2)->s1+s2);

1

Testando as duas abordagens sugeridas no Shail016 e a resposta bpedroso ( https://stackoverflow.com/a/24883180/2832140 ), o StringBuilder+ simples append(String)dentro de um forloop, parece ser executado muito mais rapidamente list.stream().map([...].

Exemplo: Esse código percorre Map<Long, List<Long>>uma cadeia de caracteres json, usando list.stream().map([...]:

if (mapSize > 0) {
    StringBuilder sb = new StringBuilder("[");

    for (Map.Entry<Long, List<Long>> entry : threadsMap.entrySet()) {

        sb.append("{\"" + entry.getKey().toString() + "\":[");
        sb.append(entry.getValue().stream().map(Object::toString).collect(Collectors.joining(",")));
    }
    sb.delete(sb.length()-2, sb.length());
    sb.append("]");
    System.out.println(sb.toString());
}

Na minha VM de desenvolvimento, o junit normalmente leva entre 0,35 e 1,2 segundos para executar o teste. Enquanto, usando este código a seguir, leva entre 0,15 e 0,33 segundos:

if (mapSize > 0) {
    StringBuilder sb = new StringBuilder("[");

    for (Map.Entry<Long, List<Long>> entry : threadsMap.entrySet()) {

        sb.append("{\"" + entry.getKey().toString() + "\":[");

        for (Long tid : entry.getValue()) {
            sb.append(tid.toString() + ", ");
        }
        sb.delete(sb.length()-2, sb.length());
        sb.append("]}, ");
    }
    sb.delete(sb.length()-2, sb.length());
    sb.append("]");
    System.out.println(sb.toString());
}

0

Podemos tentar isso?

public static void main(String []args){
        List<String> stringList = new ArrayList<>();
        for(int i=0;i< 10;i++){
            stringList.add(""+i);
        }
        String stringConcated = String.join(",", stringList);
        System.out.println(stringConcated);

    }

0
String actual = list.stream().reduce((t, u) -> t + "," + u).get();

7
Geralmente, é uma boa ideia adicionar uma explicação à sua postagem que fale sobre como o código funciona. Isso permite que novos desenvolvedores entendam o que o código faz.
Caleb Kleveter

1
O @CalebKleveter reduz a Lista um único Stringe separa cada elemento com uma vírgula ( ,).
precisa saber é o seguinte

2
Sua solução SOMENTE funciona se a lista for uma Lista <>. O OP não especificou essa classe. Em sua pergunta, mesmo uma lista <Integer> é apontada. Seu código não compila nesse caso.
RubioRic 8/11

0

Vou usar a API de streams para converter um fluxo de números inteiros em uma única string. O problema com algumas das respostas fornecidas é que elas produzem um tempo de execução O (n ^ 2) devido à construção de String. Uma solução melhor é usar um StringBuilder e, em seguida, unir as seqüências como a etapa final.

//              Create a stream of integers 
    String result = Arrays.stream(new int[]{1,2,3,4,5,6 })                
            // collect into a single StringBuilder
            .collect(StringBuilder::new, // supplier function
                    // accumulator - converts cur integer into a string and appends it to the string builder
                    (builder, cur) -> builder.append(Integer.toString(cur)),
                    // combiner - combines two string builders if running in parallel
                    StringBuilder::append) 
            // convert StringBuilder into a single string
            .toString();

Você pode levar esse processo um passo adiante convertendo a coleção de objetos em uma única sequência.

// Start with a class definition
public static class AClass {
    private int value;
    public int getValue() { return value; }
    public AClass(int value) { this.value = value; }

    @Override
    public String toString() {
        return Integer.toString(value);
    }
}

// Create a stream of AClass objects
        String resultTwo = Arrays.stream(new AClass[]{
                new AClass(1),
                new AClass(2),
                new AClass(3),
                new AClass(4)
        })
                // transform stream of objects into a single string
                .collect(StringBuilder::new,
                        (builder, curObj) -> builder.append(curObj.toString()),
                        StringBuilder::append
                )
            // finally transform string builder into a single string
            .toString();

-1

Com Java 8+

String s = Arrays.toString(list.stream().toArray(AClass[]::new));

Não é o mais eficiente, mas é uma solução com uma pequena quantidade de código.


-2

Além disso, você pode fazer assim.

    List<String> list = Arrays.asList("One", "Two", "Three");
    String result = String.join(", ", list);
    System.out.println(result);

Sua solução SOMENTE funciona se a lista for uma Lista <>. O OP não especificou essa classe. Em sua pergunta, mesmo uma lista <Integer> é apontada.
RubioRic 8/11
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.