Podemos usar o método collect para resgatar esses elementos do nosso
Stream para uma List. Porém, repare sua assinatura
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
Ela recebe três argumentos. Os três são interfaces funcionais. O primeiro é uma factory que vai criar o objeto que será devolvido no final da coleta. O segundo é o método que será invocado para adicionar cada elemento. O terceiro pode ser invocados e precisarmos adicionar mais de um elemento ao mesmo tempo (por exemplo, se formos usar uma estratégia de coletar elementos paralelamente, como veremos no futuro).
Para fazer essa transformação simples, eu teria que escrever um código como esse:
Supplier<ArrayList<Usuario>> supplier = ArrayList::new;
BiConsumer<ArrayList<Usuario>, Usuario> accumulator =
ArrayList::add;
BiConsumer<ArrayList<Usuario>, ArrayList<Usuario>> combiner =
ArrayList::addAll;
List<Usuario> maisQue100 = usuarios.stream()
.filter(u-> u.getPontos() > 100)
.collect(supplier, accumulator, combiner);
Bastante complicado, não acha? Poderíamos tentar simplificar deixando o código em um único statement, mas ainda sim teríamos uma operação complicada e perderíamos um pouco na legibilidade:
usuarios.stream()
.filter(u-> u.getPontos() > 100)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
A API simplificou esse trabalho, claro. Temos uma outra opção de uso do método collect, que recebe um Collector como parâmetro:
<R, A> R collect(Collector<? super T, A, R> collector);
A interface Collector nada mais é que alguém que tem um supplier, um accumulator e um combiner. A vantagem é que existem vários Collectors prontos.
Podemos simplificar bastante nosso código, passando como parâmetro em nosso método collect o Collectors.toList(), uma das implementações dessa nova interface.
usuarios.stream()
.filter(u-> u.getPontos() > 100)
.collect(Collectors.toList());
Collectors.toList devolve um coletor bem parecido com o qual criamos na mão, com a diferença de que ele devolve uma lista que não sabemos se é mutável, sé thread-safe ou qual a sua implementação. Fazendo um import estático podemos deixar o código um pouco mais enxuto, em um único statement.
usuarios.stream()
.filter(u-> u.getPontos() > 100)
.collect(toList());
Pronto! Agora conseguimos coletar o resultado das transformações no nosso
Stream emum List:
List<Usuario> maisQue100 = usuarios.stream()
.filter(u-> u.getPontos() > 100)
.collect(toList());
Apesar do import estático deixar nosso código mais enxuto, evitaremos abusar. O motivo é simples: para ficar mais claro durante a leitura do livro qual método estamos invocando.
7.5 Avançado: porque não há um toList em Stream?
Ausência de toList como método default
- Brian Goetz e outros líderes da comunidade Java decidiram não incluir toList diretamente na API do Java 8.
- O motivo é evitar um crescimento excessivo da API com métodos que poderiam ser úteis, mas não essenciais.
Alternativas para coletar dados de um Stream
- toSet(): Coleta os elementos em um Set.
Set<Usuario> maisQue100 = usuarios
.stream()
.filter(u -> u.getPontos() > 100)
.collect(Collectors.toSet());
- toCollection(Supplier): Permite definir a implementação da coleção retornada.
Set<Usuario> set = stream.collect(
Collectors.toCollection(HashSet::new)
);
O Supplier funciona como uma fábrica, fornecendo a estrutura desejada para os dados coletados.
- Uso de toArray() Converte um Stream em um array de Object[] ou de um tipo específico.
Usuario[] array = stream.toArray(Usuario[]::new);
Pode receber um IntSupplier para definir o tamanho do array.
Top comments (0)