Item 46: Dê preferência às funções sem efeitos colaterais nas streams
Java Efetivo (livro)
Posted on August 8, 2024
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.
Posted on August 8, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024