Item 46: Dê preferência às funções sem efeitos colaterais nas streams

javaefetivo

Java Efetivo (livro)

Posted on August 8, 2024

Item 46: Dê preferência às funções sem efeitos colaterais nas streams

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);
    });
}

Enter fullscreen mode Exit fullscreen mode

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()));
}

Enter fullscreen mode Exit fullscreen mode

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());

Enter fullscreen mode Exit fullscreen mode

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
    ));
}

Enter fullscreen mode Exit fullscreen mode

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))
    ));

Enter fullscreen mode Exit fullscreen mode

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(", ", "[", "]"));

Enter fullscreen mode Exit fullscreen mode

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.
💖 💪 🙅 🚩
javaefetivo
Java Efetivo (livro)

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