Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
API de Transmissão | enum & Stream API
Java Data Structures

API de TransmissãoAPI de Transmissão

Existem várias maneiras de processar dados em Java - loops, métodos e diferentes algoritmos. No entanto, no Java 8, foi introduzida uma ferramenta muito poderosa - a API Stream.

The Java Stream API is a functional interface in Java that provides facilities for declarative data processing as a sequence of elements. It offers concise and expressive methods for performing intermediate and terminal operations on streams of data, enhancing readability, and reducing code size when dealing with collections and other data sequences.

Em termos simples, a API de Stream é uma maneira de trabalhar de forma rápida e fácil com um fluxo de informações. No nosso caso, esse fluxo de informações é representado por coleções. A API de Stream possui alguns conceitos. Aqui estão os principais.

Conceitos Principais

  • Stream: Representa uma sequência de elementos de dados que podem ser processados.
  • Operações Intermediárias: Operações que criam um novo stream após a sua execução. Exemplos: filter, map, distinct, sorted.
  • Operações Terminais: Operações que completam o processamento do stream e retornam um resultado. Exemplos: collect, forEach, count, reduce.
  • Streams Paralelos: Permitem o processamento paralelo de dados. Os métodos parallel() e parallelStream() são utilizados para criar streams paralelos.

Chega de falar sobre teoria, vamos começar a programar!

A declaração de um stream é feita utilizando um método na coleção que desejamos transformar em stream:

java

main.java

Com o método stream(), obtivemos um fluxo de strings. Porém, para começar a trabalhar com o fluxo, precisamos entender o que são expressões lambda, pois os métodos de fluxo trabalham principalmente com elas.

Expressões Lambda

Expressões lambda foram introduzidas no Java 8 e representam uma forma simplificada de criar funções anônimas em Java. Ainda não abordamos funções anônimas anteriormente, pois não eram extremamente necessárias, mas agora vamos nos familiarizar com elas por meio das expressões lambda.

Sintaxe da Expressão Lambda:

A sintaxe geral para expressões lambda em Java é a seguinte:

java

example.java

  • Parâmetros: Esta é uma lista de parâmetros que pode estar vazia ou conter um ou mais parâmetros.
  • Seta: Representada pelo símbolo ->, que separa os parâmetros do corpo da expressão lambda.
  • Expressão ou Declarações: Este é o corpo da função, contendo uma expressão ou um bloco de instruções.

Aqui está um exemplo de uma expressão lambda representando uma função simples que soma dois números:

java

example.java

Vamos examinar mais de perto o que está acontecendo no código acima e como usamos expressões lambda:

java

main.java

Explicação:

  • Criada uma interface funcional MyMathOperation com um único método abstrato operate.
  • Utilizada uma expressão lambda para implementar este método, realizando a soma de dois números.
  • Impresso o resultado da adição.

Entendo que possa ser desafiador compreender o que está acontecendo neste código por enquanto, mas vamos voltar para a API Stream, onde expressões lambda são frequentemente utilizadas, e tentar entender como usá-las na prática.

Como você deve se lembrar, anteriormente, nós criamos um fluxo de strings a partir de uma lista de strings. Agora, vamos utilizar métodos de stream para transformar cada string deste fluxo em maiúsculas:

java

main.java

No código acima, utilizamos uma expressão lambda e dois métodos: map() e toList(). Se está claro o que o método toList() faz, o método map() altera cada elemento no fluxo de acordo com a expressão lambda fornecida.

Vamos examinar mais de perto como a expressão lambda funciona aqui:

O método map() aplica o método toUpperCase() em cada elemento do fluxo. Nós definimos o elemento deste fluxo como e e, utilizando a expressão lambda, instruímos o programa a aplicar este método a cada elemento.

Mas isso não é o fim ainda, pois aplicamos uma operação intermediária. Isso significa que as operações no fluxo ainda não foram concluídas. Para completar o trabalho no fluxo, precisamos aplicar uma operação terminal, que finalizará as operações no fluxo e retornará um valor específico. Por exemplo, podemos usar o método toList(), e o fluxo modificado será convertido em uma lista.

Por exemplo:

java

main.java

Nota

Observe que após usar a operação terminal, não é mais possível utilizar métodos de stream. No nosso caso, após a operação terminal toList(), nosso stream foi convertido em uma lista, logo não podemos usar métodos de stream na lista.

Vamos examinar mais de perto as possíveis operações intermediárias no stream.

Operações intermediárias

  • O método map() - você já está familiarizado com este método; ele realiza operações especificadas pela expressão lambda em cada elemento do fluxo.

Por exemplo, vamos utilizar o método substring() em cada elemento no fluxo de strings:

java

main.java

  • O método filter() recebe uma expressão lambda com uma condição com base na qual o fluxo será filtrado. Em outras palavras, todos os elementos que atendem à condição irão permanecer no fluxo, e elementos que não atendem à condição serão removidos do fluxo. Vamos modificar o fluxo para manter apenas os elementos cujo comprimento é maior que 5:
java

main.java

Usando o método filter(), removemos a string "with" do fluxo porque esta palavra tem menos de 5 caracteres.

Você também pode usar operações intermediárias várias vezes seguidas.

Por exemplo, podemos simplificar um pouco o código acima:

java

main.java

Ao encadear múltiplos métodos de stream juntos, é recomendável colocar cada método em uma nova linha para melhorar significativamente a legibilidade do código.

  • O método flatMap() transforma cada elemento de um fluxo em um novo fluxo e combina os resultados em um único fluxo. Em outras palavras, com esse método, podemos dividir o fluxo em fluxos, e então eles serão mesclados em um único fluxo. Por exemplo, temos uma lista de strings onde cada string pode conter mais de uma palavra, como uma lista de nomes e sobrenomes. E precisamos capitalizar a primeira letra de cada uma dessas palavras:
java

main.java

No código acima, escrevemos um método privado separado que capitaliza a primeira letra de uma palavra e utilizamos este método no método map() juntamente com uma expressão lambda.

Note que ao usar o método flatMap, dividimos cada elemento do fluxo em diferentes fluxos usando o método Arrays.stream(e.split(" ")). Como o método split() retorna um array, precisamos usar o método Arrays.stream() para dividir este array em fluxos.

Depois, todos esses fluxos são fundidos em um único fluxo, após o qual usamos o método que escrevemos. E agora temos todos os nomes e sobrenomes dos usuários com a primeira letra maiúscula.

Sabe o que seria legal? Se colocássemos esses nomes e sobrenomes em um HashMap, onde a chave é o sobrenome e o valor é o nome.

Vamos implementar isso no código:

java

main.java

Com um loop simples, armazenamos o nome e o sobrenome em variáveis e depois no mapa. Observe como o loop funciona. Incrementamos a variável i em 2 a cada iteração porque precisamos pular o sobrenome depois de já tê-lo registrado.

Nota

Este método de filtragem de dados é bastante arriscado porque os dados podem ser registrados na ordem errada, mas no nosso caso, isso não tem um papel significativo.

  • O método distinct() remove duplicatas do fluxo. Em geral, isso pode ser útil se você precisar de elementos únicos no fluxo ou se quiser eliminar rapidamente duplicatas de uma lista. Você pode facilmente conseguir isso com a seguinte construção:
  • O método sorted ordena todos os elementos do fluxo em ordem natural, do menor para o maior número ou em ordem alfabética. Isso também pode ser útil se você precisar de um fluxo ordenado ou se precisar ordenar rapidamente uma lista.
  • O método skip(n) ignora os primeiros n elementos do fluxo. Isso pode ser útil ao trabalhar com arquivos de texto, onde as primeiras n linhas podem ser, por exemplo, metadados ou uma descrição do arquivo. Vale também mencionar o método limit(n), que geralmente limita o número de elementos no fluxo. Mesmo que criemos um fluxo com 1000 elementos e depois usemos limit(200), o fluxo conterá apenas os primeiros 200 elementos.
java

main.java

Estes são os principais métodos intermediários que você precisará usar. Você pode explorar o restante dos métodos consultando a link para a documentação oficial do Java. Vamos prosseguir com os métodos terminais.

Métodos Terminais

  • O método terminal com o qual você já está familiarizado é toList(). Ele converte o fluxo em uma lista e a retorna. Em outras palavras, podemos atribuir diretamente este fluxo com métodos a uma lista. Este método foi introduzido no Java 17 e serve como substituto para a construção mais complexa collect(Collectors.toList()).
  • O método collect() também converte o fluxo em uma estrutura de dados específica. Ele utiliza, como parâmetro, um método da interface Collectors. Esta interface possui métodos como toList(), toSet() e toCollection(). Por exemplo:
java

main.java

O método forEach() recebe uma expressão lambda e realiza uma ação específica para cada elemento no stream.

Por exemplo:

java

main.java

A diferença entre este método e o método map é que este método é terminal, e após utilizá-lo, não é possível chamar outros métodos.

Estes são todos os métodos básicos para trabalhar com streams. É um tópico complexo e talvez você não o compreenda imediatamente. Contudo, é um assunto que se domina com a prática. Nos próximos capítulos práticos sobre streams, você terá várias oportunidades para trabalhar com eles, já que é uma forma muito conveniente e prática de manipular listas e arrays de dados!

1. Qual é o propósito principal da API Stream em Java?
2. Qual das seguintes é uma operação terminal na API de Stream?
3. O que a operação `map` faz na API Stream?
4. Qual é a diferença entre a operação `flatMap` e `map` na API Stream?
5. O que a operação `filter` faz na API Stream?
6. Qual é o propósito da operação `forEach` na API de Stream?
7. Qual das seguintes é uma operação intermediária na API de Stream?
8. Como é usada a operação `limit` na API Stream?

Qual é o propósito principal da API Stream em Java?

Selecione a resposta correta

Qual das seguintes é uma operação terminal na API de Stream?

Selecione a resposta correta

O que a operação map faz na API Stream?

Selecione a resposta correta

Qual é a diferença entre a operação flatMap e map na API Stream?

Selecione a resposta correta

O que a operação filter faz na API Stream?

Selecione a resposta correta

Qual é o propósito da operação forEach na API de Stream?

Selecione a resposta correta

Qual das seguintes é uma operação intermediária na API de Stream?

Selecione a resposta correta

Como é usada a operação limit na API Stream?

Selecione a resposta correta

Tudo estava claro?

Seção 4. Capítulo 3
course content

Conteúdo do Curso

Java Data Structures

API de TransmissãoAPI de Transmissão

Existem várias maneiras de processar dados em Java - loops, métodos e diferentes algoritmos. No entanto, no Java 8, foi introduzida uma ferramenta muito poderosa - a API Stream.

The Java Stream API is a functional interface in Java that provides facilities for declarative data processing as a sequence of elements. It offers concise and expressive methods for performing intermediate and terminal operations on streams of data, enhancing readability, and reducing code size when dealing with collections and other data sequences.

Em termos simples, a API de Stream é uma maneira de trabalhar de forma rápida e fácil com um fluxo de informações. No nosso caso, esse fluxo de informações é representado por coleções. A API de Stream possui alguns conceitos. Aqui estão os principais.

Conceitos Principais

  • Stream: Representa uma sequência de elementos de dados que podem ser processados.
  • Operações Intermediárias: Operações que criam um novo stream após a sua execução. Exemplos: filter, map, distinct, sorted.
  • Operações Terminais: Operações que completam o processamento do stream e retornam um resultado. Exemplos: collect, forEach, count, reduce.
  • Streams Paralelos: Permitem o processamento paralelo de dados. Os métodos parallel() e parallelStream() são utilizados para criar streams paralelos.

Chega de falar sobre teoria, vamos começar a programar!

A declaração de um stream é feita utilizando um método na coleção que desejamos transformar em stream:

java

main.java

Com o método stream(), obtivemos um fluxo de strings. Porém, para começar a trabalhar com o fluxo, precisamos entender o que são expressões lambda, pois os métodos de fluxo trabalham principalmente com elas.

Expressões Lambda

Expressões lambda foram introduzidas no Java 8 e representam uma forma simplificada de criar funções anônimas em Java. Ainda não abordamos funções anônimas anteriormente, pois não eram extremamente necessárias, mas agora vamos nos familiarizar com elas por meio das expressões lambda.

Sintaxe da Expressão Lambda:

A sintaxe geral para expressões lambda em Java é a seguinte:

java

example.java

  • Parâmetros: Esta é uma lista de parâmetros que pode estar vazia ou conter um ou mais parâmetros.
  • Seta: Representada pelo símbolo ->, que separa os parâmetros do corpo da expressão lambda.
  • Expressão ou Declarações: Este é o corpo da função, contendo uma expressão ou um bloco de instruções.

Aqui está um exemplo de uma expressão lambda representando uma função simples que soma dois números:

java

example.java

Vamos examinar mais de perto o que está acontecendo no código acima e como usamos expressões lambda:

java

main.java

Explicação:

  • Criada uma interface funcional MyMathOperation com um único método abstrato operate.
  • Utilizada uma expressão lambda para implementar este método, realizando a soma de dois números.
  • Impresso o resultado da adição.

Entendo que possa ser desafiador compreender o que está acontecendo neste código por enquanto, mas vamos voltar para a API Stream, onde expressões lambda são frequentemente utilizadas, e tentar entender como usá-las na prática.

Como você deve se lembrar, anteriormente, nós criamos um fluxo de strings a partir de uma lista de strings. Agora, vamos utilizar métodos de stream para transformar cada string deste fluxo em maiúsculas:

java

main.java

No código acima, utilizamos uma expressão lambda e dois métodos: map() e toList(). Se está claro o que o método toList() faz, o método map() altera cada elemento no fluxo de acordo com a expressão lambda fornecida.

Vamos examinar mais de perto como a expressão lambda funciona aqui:

O método map() aplica o método toUpperCase() em cada elemento do fluxo. Nós definimos o elemento deste fluxo como e e, utilizando a expressão lambda, instruímos o programa a aplicar este método a cada elemento.

Mas isso não é o fim ainda, pois aplicamos uma operação intermediária. Isso significa que as operações no fluxo ainda não foram concluídas. Para completar o trabalho no fluxo, precisamos aplicar uma operação terminal, que finalizará as operações no fluxo e retornará um valor específico. Por exemplo, podemos usar o método toList(), e o fluxo modificado será convertido em uma lista.

Por exemplo:

java

main.java

Nota

Observe que após usar a operação terminal, não é mais possível utilizar métodos de stream. No nosso caso, após a operação terminal toList(), nosso stream foi convertido em uma lista, logo não podemos usar métodos de stream na lista.

Vamos examinar mais de perto as possíveis operações intermediárias no stream.

Operações intermediárias

  • O método map() - você já está familiarizado com este método; ele realiza operações especificadas pela expressão lambda em cada elemento do fluxo.

Por exemplo, vamos utilizar o método substring() em cada elemento no fluxo de strings:

java

main.java

  • O método filter() recebe uma expressão lambda com uma condição com base na qual o fluxo será filtrado. Em outras palavras, todos os elementos que atendem à condição irão permanecer no fluxo, e elementos que não atendem à condição serão removidos do fluxo. Vamos modificar o fluxo para manter apenas os elementos cujo comprimento é maior que 5:
java

main.java

Usando o método filter(), removemos a string "with" do fluxo porque esta palavra tem menos de 5 caracteres.

Você também pode usar operações intermediárias várias vezes seguidas.

Por exemplo, podemos simplificar um pouco o código acima:

java

main.java

Ao encadear múltiplos métodos de stream juntos, é recomendável colocar cada método em uma nova linha para melhorar significativamente a legibilidade do código.

  • O método flatMap() transforma cada elemento de um fluxo em um novo fluxo e combina os resultados em um único fluxo. Em outras palavras, com esse método, podemos dividir o fluxo em fluxos, e então eles serão mesclados em um único fluxo. Por exemplo, temos uma lista de strings onde cada string pode conter mais de uma palavra, como uma lista de nomes e sobrenomes. E precisamos capitalizar a primeira letra de cada uma dessas palavras:
java

main.java

No código acima, escrevemos um método privado separado que capitaliza a primeira letra de uma palavra e utilizamos este método no método map() juntamente com uma expressão lambda.

Note que ao usar o método flatMap, dividimos cada elemento do fluxo em diferentes fluxos usando o método Arrays.stream(e.split(" ")). Como o método split() retorna um array, precisamos usar o método Arrays.stream() para dividir este array em fluxos.

Depois, todos esses fluxos são fundidos em um único fluxo, após o qual usamos o método que escrevemos. E agora temos todos os nomes e sobrenomes dos usuários com a primeira letra maiúscula.

Sabe o que seria legal? Se colocássemos esses nomes e sobrenomes em um HashMap, onde a chave é o sobrenome e o valor é o nome.

Vamos implementar isso no código:

java

main.java

Com um loop simples, armazenamos o nome e o sobrenome em variáveis e depois no mapa. Observe como o loop funciona. Incrementamos a variável i em 2 a cada iteração porque precisamos pular o sobrenome depois de já tê-lo registrado.

Nota

Este método de filtragem de dados é bastante arriscado porque os dados podem ser registrados na ordem errada, mas no nosso caso, isso não tem um papel significativo.

  • O método distinct() remove duplicatas do fluxo. Em geral, isso pode ser útil se você precisar de elementos únicos no fluxo ou se quiser eliminar rapidamente duplicatas de uma lista. Você pode facilmente conseguir isso com a seguinte construção:
  • O método sorted ordena todos os elementos do fluxo em ordem natural, do menor para o maior número ou em ordem alfabética. Isso também pode ser útil se você precisar de um fluxo ordenado ou se precisar ordenar rapidamente uma lista.
  • O método skip(n) ignora os primeiros n elementos do fluxo. Isso pode ser útil ao trabalhar com arquivos de texto, onde as primeiras n linhas podem ser, por exemplo, metadados ou uma descrição do arquivo. Vale também mencionar o método limit(n), que geralmente limita o número de elementos no fluxo. Mesmo que criemos um fluxo com 1000 elementos e depois usemos limit(200), o fluxo conterá apenas os primeiros 200 elementos.
java

main.java

Estes são os principais métodos intermediários que você precisará usar. Você pode explorar o restante dos métodos consultando a link para a documentação oficial do Java. Vamos prosseguir com os métodos terminais.

Métodos Terminais

  • O método terminal com o qual você já está familiarizado é toList(). Ele converte o fluxo em uma lista e a retorna. Em outras palavras, podemos atribuir diretamente este fluxo com métodos a uma lista. Este método foi introduzido no Java 17 e serve como substituto para a construção mais complexa collect(Collectors.toList()).
  • O método collect() também converte o fluxo em uma estrutura de dados específica. Ele utiliza, como parâmetro, um método da interface Collectors. Esta interface possui métodos como toList(), toSet() e toCollection(). Por exemplo:
java

main.java

O método forEach() recebe uma expressão lambda e realiza uma ação específica para cada elemento no stream.

Por exemplo:

java

main.java

A diferença entre este método e o método map é que este método é terminal, e após utilizá-lo, não é possível chamar outros métodos.

Estes são todos os métodos básicos para trabalhar com streams. É um tópico complexo e talvez você não o compreenda imediatamente. Contudo, é um assunto que se domina com a prática. Nos próximos capítulos práticos sobre streams, você terá várias oportunidades para trabalhar com eles, já que é uma forma muito conveniente e prática de manipular listas e arrays de dados!

1. Qual é o propósito principal da API Stream em Java?
2. Qual das seguintes é uma operação terminal na API de Stream?
3. O que a operação `map` faz na API Stream?
4. Qual é a diferença entre a operação `flatMap` e `map` na API Stream?
5. O que a operação `filter` faz na API Stream?
6. Qual é o propósito da operação `forEach` na API de Stream?
7. Qual das seguintes é uma operação intermediária na API de Stream?
8. Como é usada a operação `limit` na API Stream?

Qual é o propósito principal da API Stream em Java?

Selecione a resposta correta

Qual das seguintes é uma operação terminal na API de Stream?

Selecione a resposta correta

O que a operação map faz na API Stream?

Selecione a resposta correta

Qual é a diferença entre a operação flatMap e map na API Stream?

Selecione a resposta correta

O que a operação filter faz na API Stream?

Selecione a resposta correta

Qual é o propósito da operação forEach na API de Stream?

Selecione a resposta correta

Qual das seguintes é uma operação intermediária na API de Stream?

Selecione a resposta correta

Como é usada a operação limit na API Stream?

Selecione a resposta correta

Tudo estava claro?

Seção 4. Capítulo 3
some-alt