Eu tenho uma lista na myListToParse
qual desejo filtrar os elementos e aplicar um método em cada elemento e adicionar o resultado em outra lista myFinalList
.
Com o Java 8, notei que posso fazer isso de duas maneiras diferentes. Gostaria de saber a maneira mais eficiente entre eles e entender por que um caminho é melhor que o outro.
Estou aberto a qualquer sugestão sobre uma terceira via.
Método 1:
myFinalList = new ArrayList<>();
myListToParse.stream()
.filter(elt -> elt != null)
.forEach(elt -> myFinalList.add(doSomething(elt)));
Método 2:
myFinalList = myListToParse.stream()
.filter(elt -> elt != null)
.map(elt -> doSomething(elt))
.collect(Collectors.toList());
java
java-8
java-stream
Emilien Brigand
fonte
fonte
elt -> elt != null
pode ser substituído porObjects::nonNull
Optional<T>
em vez disso , use-os em combinação comflatMap
..map(this::doSomething)
assumindo que estedoSomething
é um método não estático. Se for estático, você pode substituirthis
pelo nome da classe.Respostas:
Não se preocupe com diferenças de desempenho, elas serão mínimas neste caso normalmente.
O método 2 é preferível porque
não requer a mutação de uma coleção que existe fora da expressão lambda,
é mais legível porque as diferentes etapas executadas no pipeline de coleta são gravadas sequencialmente: primeiro uma operação de filtro, depois uma operação de mapa e, em seguida, coletando o resultado (para obter mais informações sobre os benefícios dos pipelines de coleta, consulte o excelente artigo de Martin Fowler ),
você pode alterar facilmente a maneira como os valores são coletados, substituindo o
Collector
que é usado. Em alguns casos, pode ser necessário escrever o seuCollector
, mas o benefício é que você pode reutilizá-lo facilmente.fonte
Concordo com as respostas existentes de que a segunda forma é melhor porque não tem efeitos colaterais e é mais fácil paralelizar (basta usar um fluxo paralelo).
Em termos de desempenho, parece que eles são equivalentes até você começar a usar fluxos paralelos. Nesse caso, o mapa terá um desempenho muito melhor. Veja abaixo os resultados da micro benchmark :
Você não pode impulsionar o primeiro exemplo da mesma maneira, porque forEach é um método terminal - ele retorna nulo -, então você é forçado a usar uma lambda com estado. Mas isso é realmente uma péssima idéia se você estiver usando fluxos paralelos .
Por fim, observe que seu segundo trecho pode ser escrito de maneira um pouco mais concisa com referências de método e importações estáticas:
fonte
Um dos principais benefícios do uso de fluxos é que ele oferece a capacidade de processar dados de maneira declarativa, ou seja, usando um estilo funcional de programação. Ele também oferece a capacidade de multiencadeamento para um significado gratuito, não sendo necessário escrever nenhum código multithread extra para tornar seu fluxo simultâneo.
Supondo que você esteja explorando esse estilo de programação é que deseja explorar esses benefícios, seu primeiro exemplo de código não é funcional, pois o
foreach
método é classificado como terminal (o que significa que ele pode produzir efeitos colaterais).A segunda maneira é preferida do ponto de vista da programação funcional, pois a função map pode aceitar funções lambda sem estado. Mais explicitamente, o lambda passado para a função map deve ser
ArrayList
).Outro benefício com a segunda abordagem é que se o fluxo for paralelo e o coletor for simultâneo e não ordenado, essas características podem fornecer dicas úteis para a operação de redução para realizar a coleta simultaneamente.
fonte
Se você usar o Eclipse Collections, poderá usar o
collectIf()
métodoEle avalia avidamente e deve ser um pouco mais rápido do que usar um Stream.
Nota: Sou um colaborador das Coleções Eclipse.
fonte
Eu prefiro o segundo caminho.
Ao usar a primeira maneira, se você decidir usar um fluxo paralelo para melhorar o desempenho, não terá controle sobre a ordem em que os elementos serão adicionados à lista de saída por
forEach
.Quando você usa
toList
, a API do Streams preservará a ordem, mesmo se você usar um fluxo paralelo.fonte
forEachOrdered
vez deforEach
se quisesse usar um fluxo paralelo, mas ainda preservar a ordem. Mas, como a documentação para osforEach
estados, preservar a ordem do encontro sacrifica o benefício do paralelismo. Eu suspeito que também é o caso comtoList
então.Existe uma terceira opção - using
stream().toArray()
- veja os comentários em por que o stream não possui um método toList . Ele é mais lento que forEach () ou collect () e menos expressivo. Pode ser otimizado em versões posteriores do JDK, adicionando-o aqui apenas por precaução.assumindo
List<String>
com uma referência micro-micro, entradas de 1 milhão, 20% de nulos e transformação simples em doSomething ()
os resultados são
paralelo:
sequencial:
paralelo sem nulos e filtro (portanto, o fluxo é
SIZED
): toArrays tem o melhor desempenho nesse caso e.forEach()
falha com "indexOutOfBounds" no destinatário ArrayList, teve que substituir por.forEachOrdered()
fonte
Pode ser o método 3.
Eu sempre prefiro manter a lógica separada.
fonte
Se o uso de 3rd Pary Libaries estiver ok, cyclops- react define coleções estendidas preguiçosas com essa funcionalidade incorporada. Por exemplo, poderíamos simplesmente escrever
ListX myListToParse;
ListX myFinalList = myListToParse.filter (elt -> elt! = Nulo) .map (elt -> doSomething (elt));
myFinalList não é avaliado até o primeiro acesso (e depois que a lista materializada é armazenada em cache e reutilizada).
[Divulgação Sou o principal desenvolvedor do cyclops-react]
fonte