Introdução ao uso de streams:
- Novos usuários podem achar difícil expressar cálculos em pipelines de stream.
- Streams são baseadas em programação funcional, oferecendo expressividade, rapidez e paralelização.
Estruturação do cálculo:
- Estruturar cálculos como sequências de transformações usando funções puras.
- Funções puras dependem apenas de suas entradas e não alteram estado.
Efeitos colaterais:
- Evitar efeitos colaterais em funções passadas para operações de stream.
- Uso inadequado de forEach que altera estado externo é um "bad smell".
Exemplo 1: Código com efeitos colaterais
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
words.forEach(word -> {
freq.merge(word.toLowerCase(), 1L, Long::sum);
});
}
Problema: Esse código usa forEach para modificar o estado externo (freq). Ele é iterativo e não aproveita as vantagens das streams.
Exemplo 2: Código sem efeitos colaterais
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
freq = words.collect(Collectors.groupingBy(String::toLowerCase, Collectors.counting()));
}
Solução: Utiliza o coletor Collectors.groupingBy para criar a tabela de frequência sem alterar o estado externo. Mais curto, claro e eficiente.
Apropriação da API de streams:
- O código que imita loops iterativos não tira vantagem das streams.
- Utilizar coletores (Collector) para operações mais eficientes e legíveis.
Coletores:
- Simplificam a coleta de resultados em coleções como listas e conjuntos.
- Collectors.toList(), Collectors.toSet(), Collectors.toCollection(collectionFactory).
Exemplo 3: Extraindo uma lista das dez palavras mais frequentes
List<String> topTen = freq.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Explicação:
- Ordena as entradas do mapa de frequência em ordem decrescente de valor.
- Limita a stream a 10 palavras.
- Coleta as palavras mais frequentes em uma lista.
Complexidade da API Collectors:
- API possui 39 métodos, mas muitos são para uso avançado.
- Coletores podem ser usados para criar mapas (toMap, groupingBy).
Mapas e estratégias de coleta:
- toMap(keyMapper, valueMapper) para chave-valor únicos.
- Estratégias para lidar com conflitos de chaves usando função merge.
- groupingBy para agrupar elementos em categorias baseadas em funções classificadoras.
Exemplo 4: Usando toMap com função merge
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
freq = words.collect(Collectors.toMap(
String::toLowerCase,
word -> 1L,
Long::sum
));
}
Explicação:
- toMap mapeia palavras para suas frequências.
- Função merge (Long::sum) lida com conflitos de chave, somando as frequências.
Exemplo 5: Agrupando álbuns por artista e encontrando o álbum mais vendido
Map<Artist, Album> topAlbums = albums.stream()
.collect(Collectors.toMap(
Album::getArtist,
Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(Album::sales))
));
Explicação:
- toMap mapeia artistas para seus álbuns mais vendidos.
- BinaryOperator.maxBy determina o álbum mais vendido para cada artista.
Coleta de strings:
Collectors.joining para concatenar strings com delimitadores opcionais.
Exemplo 6: Concatenando strings com delimitador
String result = Stream.of("came", "saw", "conquered")
.collect(Collectors.joining(", ", "[", "]"));
Explicação:
- Collectors.joining concatena strings com uma vírgula como delimitador, prefixo e sufixo.
- Resultado: [came, saw, conquered].
Conclusão:
- Essência das streams está em funções sem efeitos colaterais.
- forEach deve ser usado apenas para reportar resultados.
- Conhecimento sobre coletores é essencial para uso eficaz das streams.
Top comments (0)