Introdução ao StreamEx

Introdução ao StreamEx

1. Visão geral

Um dos recursos mais interessantes do Java 8 é o link:/java-8-streams [Stream API] - que, simplesmente, é uma ferramenta poderosa para processar sequências de elementos.

_https://amaembo.github.io/streamex/javadoc/[StreamEx] _ é uma biblioteca que fornece funcionalidade adicional para a API Stream padrão, juntamente com as melhorias de desempenho.

Aqui estão alguns recursos principais:

  • Maneiras mais curtas e convenientes de executar as tarefas diárias

  • 100% de compatibilidade com o JDK original Streams

  • Simpatia para processamento paralelo: qualquer novo recurso aproveita ao máximo os fluxos paralelos *Desempenho e sobrecarga mínima. Se StreamEx permitir resolver a tarefa usando menos código em comparação com o _Stream padrão, _ não deve ser significativamente mais lento que o normal (e às vezes é ainda mais rápido)

Neste tutorial, apresentaremos alguns dos recursos da API StreamEx.

===* 2. Configurando o exemplo *

Para usar StreamEx, precisamos adicionar a seguinte dependência ao pom.xml:

<dependency>
    <groupId>one.util</groupId>
    <artifactId>streamex</artifactId>
    <version>0.6.5</version>
</dependency>

A versão mais recente da biblioteca pode ser encontrada em Maven Central.

Neste tutorial, vamos usar uma classe User simples:

public class User {
    int id;
    String name;
    Role role = new Role();

   //standard getters, setters, and constructors
}

E uma simples classe Role:

public class Role {
}

===* 3. Métodos de atalho dos coletores *

Uma das operações de terminal mais populares do Streams é a operação collect; isso permite reembalar elementos Stream para uma coleção de nossa escolha.

O problema é que o código pode ficar desnecessariamente detalhado para cenários simples:

users.stream()
  .map(User::getName)
  .collect(Collectors.toList());

3.1. Coletando em uma coleção

Agora, com o StreamEx, não precisamos fornecer um Collector para especificar que precisamos de List, _Set, Map, InmutableList, _ etc .:

List<String> userNames = StreamEx.of(users)
  .map(User::getName)
  .toList();
*A operação _collect_ ainda está disponível na API se queremos executar algo mais complicado do que pegar elementos de um _Stream_ e colocá-los em uma coleção.*

3.2. Coletores avançados

Outra abreviação é groupingBy:

Map<Role, List<User>> role2users = StreamEx.of(users)
  .groupingBy(User::getRole);

Isso produzirá um Map com o tipo de chave especificado na referência do método, produzindo algo semelhante ao grupo pela operação no SQL.

Usando a API Stream simples, precisaríamos escrever:

Map<Role, List<User>> role2users = users.stream()
  .collect(Collectors.groupingBy(User::getRole));

Um formulário abreviado semelhante pode ser encontrado para _Collectors.joining (): _

StreamEx.of(1, 2, 3)
  .joining("; ");//"1; 2; 3"

O que leva todos os elementos no Stream a produz um String concatenando todos eles.

*4. Adicionando, removendo e selecionando elementos *

Em alguns cenários,* temos uma lista de objetos de tipos diferentes e precisamos filtrá-los por tipo: *

List usersAndRoles = Arrays.asList(new User(), new Role());
List<Role> roles = StreamEx.of(usersAndRoles)
  .select(Role.class)
  .toList();
*Podemos adicionar elementos ao início ou ao final do nosso* _ *Stream* , _ com estas operações úteis:
List<String> appendedUsers = StreamEx.of(users)
  .map(User::getName)
  .prepend("(none)")
  .append("LAST")
  .toList();
*Podemos remover elementos nulos indesejados usando _nonNull () _* e usar o _Stream_ como um _Iterable_:
for (String line : StreamEx.of(users).map(User::getName).nonNull()) {
    System.out.println(line);
}

*5. Suporte a operações matemáticas e tipos primitivos *

StreamEx adiciona suporte para tipos primitivos, como podemos ver neste exemplo autoexplicativo:

short[] src = {1,2,3};
char[] output = IntStreamEx.of(src)
  .map(x -> x* 5)
  .toCharArray();

Agora vamos dar uma matriz de elementos double de maneira desordenada. Queremos criar uma matriz que consiste na diferença entre cada par.

Podemos usar o método pairMap para executar esta operação:

public double[] getDiffBetweenPairs(double... numbers) {
    return DoubleStreamEx.of(numbers)
      .pairMap((a, b) -> b - a)
      .toArray();
}

6. Operações do mapa

6.1. Filtrando por Chaves

Outro recurso útil é a capacidade de criar um Stream a partir de um Map e filtrar os elementos usando os valores para os quais eles apontam.

Nesse caso, estamos pegando todos os valores não nulos:

Map<String, Role> nameToRole = new HashMap<>();
nameToRole.put("first", new Role());
nameToRole.put("second", null);
Set<String> nonNullRoles = StreamEx.ofKeys(nameToRole, Objects::nonNull)
  .toSet();

6.2. Operando em pares de valor-chave

Também podemos operar em pares de valores-chave criando uma instância EntryStream:

public Map<User, List<Role>> transformMap(
    Map<Role, List<User>> role2users) {
    Map<User, List<Role>> users2roles = EntryStream.of(role2users)
     .flatMapValues(List::stream)
     .invert()
     .grouping();
    return users2roles;
}

A operação especial EntryStream.of pega um Map e o transforma em um Stream de objetos de valor-chave. Em seguida, usamos a operação flatMapValues para transformar nossa lista de funções em um Stream de valores únicos.

Em seguida, podemos inverter o par chave-valor, transformando a classe User na chave e a classe Role no valor.

E, finalmente, podemos usar a operação grouping para transformar nosso mapa na inversão do que foi recebido, todos com apenas quatro operações.

6.3. Mapeamento de valor-chave

Também podemos mapear chaves e valores independentemente:

Map<String, String> mapToString = EntryStream.of(users2roles)
  .mapKeys(String::valueOf)
  .mapValues(String::valueOf)
  .toMap();

Com isso, podemos transformar rapidamente nossas chaves ou valores em outro tipo necessário.

*7. Operações de arquivo *

Usando StreamEx, podemos ler arquivos com eficiência, ou seja, sem carregar arquivos completos de uma só vez. É útil ao processar arquivos grandes:

StreamEx.ofLines(reader)
  .remove(String::isEmpty)
  .forEach(System.out::println);

Observe que usamos o método _remove () _ para filtrar as linhas vazias.

O ponto a ser observado aqui é que o StreamEx não fechará o arquivo automaticamente. Portanto, devemos lembrar de executar manualmente a operação de fechamento na ocasião da leitura e gravação de arquivos, para evitar sobrecarga desnecessária de memória.

===* 8. Conclusão*

Neste tutorial, aprendemos sobre o StreamEx e seus diferentes utilitários. Há muito mais a ser percorrido - e eles têm uma útil citação aqui.

Como sempre, o código fonte completo está disponível over no GitHub.