Recuperando uma lista de um java.util.stream.Stream no Java 8


443

Eu estava brincando com o Java 8 lambdas para filtrar facilmente as coleções. Mas não encontrei uma maneira concisa de recuperar o resultado como uma nova lista na mesma declaração. Aqui está a minha abordagem mais concisa até agora:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = new ArrayList<>();
sourceLongList.stream().filter(l -> l > 100).forEach(targetLongList::add);

Os exemplos na rede não responderam à minha pergunta porque eles param sem gerar uma nova lista de resultados. Deve haver uma maneira mais concisa. Eu teria esperado que a Streamclasse tem métodos como toList(), toSet()...

Existe alguma maneira de targetLongListatribuir diretamente as variáveis pela terceira linha?


7
No caso de você não precisar sourceLongListdepois, Collection.removeIf(…)por conveniência.
Holger

2
Que tal agora? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());
Leeyuiwah

Respostas:


609

O que você está fazendo pode ser a maneira mais simples, desde que o fluxo permaneça sequencial; caso contrário, você precisará fazer uma chamada para sequencial () antes forEach.

[editar mais tarde: a razão pela qual a chamada para seqüencial () é necessária é que o código como está ( forEach(targetLongList::add)) seria atrevido se o fluxo fosse paralelo. Mesmo assim, ele não alcançará o efeito pretendido, como forEaché explicitamente não determinístico - mesmo em um fluxo seqüencial a ordem do processamento do elemento não é garantida. Você precisaria usar forEachOrderedpara garantir a ordem correta. A intenção dos designers da API de fluxo é que você use o coletor nesta situação, como abaixo.]

Uma alternativa é

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());

10
Além disso: acho que esses códigos ficam um pouco mais curtos, claros e mais bonitos se você usar uma importação estática de toList. Isso é feito colocando o seguinte entre as importações do arquivo: static import java.util.stream.Collectors.toList;. Em seguida, a chamada a cobrar será exibida apenas .collect(toList()).
Lii

4
No Eclipse, é possível fazer o IDE adicionar uma importação estática para métodos. Isso é feito adicionando a Collectorsclasse em Preferências -> Java -> Editor -> Assistente de Conteúdo -> Favoritos . Depois disso, você só precisará digitar toLino hit Ctr + Space para que o IDE preencha toListe adicione a importação estática.
Lii

3
Uma coisa a ter em mente é que IntStreamalgumas outras quase-mas-não-muito-não- Streamtêm o collect(Collector)método e você terá que ligar IntStream.boxed()para convertê-las para uma regular Streamprimeiro. Então, novamente, talvez você só queira toArray().
Mutant Bob

por isso temos que usar sequential()antes forEachou use 'forEachOrdered`
Amarnath harish

1
@amarnathharish Porque o forEach não garante a ordem de execução da operação para um fluxo paralelo. O JavaDoc diz "O comportamento desta operação é explicitamente não determinístico. Para pipelines de fluxo paralelo, essa operação não garante o respeito à ordem de encontro do fluxo, pois isso sacrificaria o benefício do paralelismo". (A primeira frase de estas citações na verdade, significa que a ordem não é garantida para fluxos sequenciais quer, embora na prática seja preservada.)
Maurice Naftalin

183

Atualizada:

Outra abordagem é usar Collectors.toList:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toList());

Solução anterior:

Outra abordagem é usar Collectors.toCollection:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));

60
No entanto, isso é útil se você deseja uma implementação específica da Lista.
orbfish

1
Apesar de ser recomendado codificar contra interfaces, há casos claros (um deles é o GWT) quando você precisa codificar contra implementações concretas (a menos que queira que todas as implementações da lista sejam compiladas e entregues como javascript).
Eduard Korenschi / 01/15

3
Outro profissional desse método, do Collectors::toListjavadoc: "Não há garantias quanto ao tipo, mutabilidade, serialização ou segurança de thread da Lista retornada; se for necessário mais controle sobre a Lista retornada, use toCollection(Supplier)".
Charles Wood

12

Eu gosto de usar um método util que retorna um coletor para ArrayListquando é isso que eu quero.

Acho que a solução utilizada Collectors.toCollection(ArrayList::new)é um pouco barulhenta para uma operação tão comum.

Exemplo:

ArrayList<Long> result = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

Com esta resposta, também quero demonstrar como é simples criar e usar coletores personalizados, o que é muito útil em geral.


Se você declarar o resultado como Lista <Long>, não precisará usar este método utilitário. Collectors.toList fará. Além disso, o uso de classes específicas em vez de interfaces é um cheiro de código.
Lluis Martinez

1
@LluisMartinez: "Collectors.toList serve ." : Não, não em muitas situações. Porque não é uma boa ideia usar toListse você, por exemplo, deseja modificar a lista posteriormente no programa. A toListdocumentação diz o seguinte: "Não há garantias quanto ao tipo, mutabilidade, serialização ou segurança de thread da Lista retornada; se for necessário mais controle sobre a Lista retornada, use toCollection". . Minha resposta demonstra uma maneira de tornar mais conveniente fazer isso em um caso comum.
Lii

Se você deseja criar especificamente um ArrayList, tudo bem.
Lluis Martinez

5

Se você tiver uma matriz de primitivas, poderá usar as coleções primitivas disponíveis nas Coleções Eclipse .

LongList sourceLongList = LongLists.mutable.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
LongList targetLongList = sourceLongList.select(l -> l > 100);

Se você não pode alterar o sourceLongList de List:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = 
    ListAdapter.adapt(sourceLongList).select(l -> l > 100, new ArrayList<>());

Se você deseja usar LongStream:

long[] sourceLongs = new long[]{1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L};
LongList targetList = 
    LongStream.of(sourceLongs)
    .filter(l -> l > 100)
    .collect(LongArrayList::new, LongArrayList::add, LongArrayList::addAll);

Nota: Sou colaborador do Eclipse Collections.


4

Uma maneira um pouco mais eficiente (evite criar a Lista de origem e o unboxing automático pelo filtro):

List<Long> targetLongList = LongStream.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L)
    .filter(l -> l > 100)
    .boxed()
    .collect(Collectors.toList());


1

Se você não se importa em usar bibliotecas de terceiros, a lib cyclops-react da AOL (divulgação eu sou um colaborador) possui extensões para todos os tipos de coleções do JDK , incluindo a List . A interface do ListX estende o java.util.List e adiciona um grande número de operadores úteis, incluindo o filtro.

Você pode simplesmente escrever

ListX<Long> sourceLongList = ListX.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
ListX<Long> targetLongList = sourceLongList.filter(l -> l > 100);

O ListX também pode ser criado a partir de uma lista existente (via ListX.fromIterable)


1

Existe uma outra variante do método collect fornecida pela classe LongStream e da mesma forma pelas classes IntStream e DoubleStream também.

<R> R collect(Supplier<R> supplier,
              ObjLongConsumer<R> accumulator,
              BiConsumer<R,R> combiner)

Executa uma operação de redução mutável nos elementos desse fluxo. Uma redução mutável é aquela em que o valor reduzido é um contêiner de resultado mutável, como um ArrayList, e os elementos são incorporados atualizando o estado do resultado e não substituindo o resultado. Isso produz um resultado equivalente a:

R result = supplier.get();
  for (long element : this stream)
       accumulator.accept(result, element);
  return result;

Como reduzir (long, LongBinaryOperator), as operações de coleta podem ser paralelizadas sem a necessidade de sincronização adicional. Esta é uma operação do terminal.

E a resposta à sua pergunta com este método de coleta é a seguinte:

    LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, (list, value) -> list.add(value)
    , (list1, list2) -> list1.addAll(list2));

Abaixo está a variante de referência do método, que é bastante inteligente, mas algumas são difíceis de entender:

     LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, List::add , List::addAll);

Abaixo, a variante HashSet:

     LongStream.of(1L, 2L, 3L, 3).filter(i -> i > 2)
     .collect(HashSet::new, HashSet::add, HashSet::addAll);

Da mesma forma, a variante LinkedList é assim:

     LongStream.of(1L, 2L, 3L, 3L)
     .filter(i -> i > 2)
     .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);

0

Caso alguém (como eu) esteja procurando maneiras de lidar com objetos em vez de tipos primitivos, use mapToObj()

String ss = "An alternative way is to insert the following VM option before "
        + "the -vmargs option in the Eclipse shortcut properties(edit the "
        + "field Target inside the Shortcut tab):";

List<Character> ll = ss
                        .chars()
                        .mapToObj(c -> new Character((char) c))
                        .collect(Collectors.toList());

System.out.println("List type: " + ll.getClass());
System.out.println("Elem type: " + ll.get(0).getClass());
ll.stream().limit(50).forEach(System.out::print);

impressões:

List type: class java.util.ArrayList
Elem type: class java.lang.Character
An alternative way is to insert the following VM o

0
String joined = 
                Stream.of(isRead?"read":"", isFlagged?"flagged":"", isActionRequired?"action":"", isHide?"hide":"")
                      .filter(s -> s != null && !s.isEmpty())
                      .collect(Collectors.joining(","));

0

Aqui está o código de AbacusUtil

LongStream.of(1, 10, 50, 80, 100, 120, 133, 333).filter(e -> e > 100).toList();

Divulgação: sou o desenvolvedor do AbacusUtil.


Não encontro nenhum método toList presente na classe LongStream. Você poderia executar este código?
Vaneet Kataria

@VaneetKataria try com.landawn.abacus.util.stream.LongStreamor LongStreamExin AbacusUtil
#

0

Você pode reescrever o código como abaixo:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = sourceLongList.stream().filter(l -> l > 100).collect(Collectors.toList());

Obrigado pela sua contribuição. No entanto, explique o que você mudou e até que ponto isso se relaciona com a pergunta.
precisa

Aqui, primeiro converti meu ArrayList para vaporizá-los usando o filtro, filtro os dados necessários. Finalmente, usei o método collect do java 8 stream para coletar dados em uma nova lista chamada targetLongList.
Pratik Pawar

0

Para coletar em uma lista mutável:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toList());

Para coletar em uma lista imutável:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toUnmodifiableList());

Explicação dos collectdo JavaDoc :

Executa uma operação de redução mutável nos elementos desse fluxo usando um Coletor. Um coletor encapsula as funções usadas como argumentos para coletar (Fornecedor, BiConsumer, BiConsumer), permitindo a reutilização de estratégias de coleta e composição de operações de coleta, como agrupamento ou particionamento em vários níveis. Se o fluxo for paralelo, e o Coletor for simultâneo e o fluxo não for ordenado ou o coletor não for ordenado, será executada uma redução simultânea (consulte Coletor para obter detalhes sobre a redução simultânea).

Esta é uma operação do terminal.

Quando executados em paralelo, vários resultados intermediários podem ser instanciados, preenchidos e mesclados para manter o isolamento de estruturas de dados mutáveis. Portanto, mesmo quando executado em paralelo com estruturas de dados não thread-safe (como ArrayList), nenhuma sincronização adicional é necessária para uma redução paralela.


-3

Se você não usar parallel()isso funcionará

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);

List<Long> targetLongList =  new ArrayList<Long>();

sourceLongList.stream().peek(i->targetLongList.add(i)).collect(Collectors.toList());

Não gosto que o collect () seja usado apenas para conduzir o fluxo, de modo que o gancho peek () seja chamado em cada item. O resultado da operação do terminal é descartado.
Daniel K.

É muito estranho ligar collecte depois não salvar o valor de retorno. Nesse caso, você pode usar forEach. Mas essa ainda é uma solução ruim.
Lii

1
Usar peek () dessa maneira é um antipadrão.
Grzegorz Piwowarek

De acordo com o método de visualização de documentos Java do Stream, deve ser usado apenas para fins de depuração. Não deve ser usado para qualquer outro processamento que não seja a depuração.
Vaneet Kataria
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.