API 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.
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()
eparallelStream()
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:
main.java
12List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream();
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:
example.java
123(parameters) -> expression // or (parameters) -> { statements; }
-
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:
example.java
12345678910// Traditional way MathOperation addition = new MathOperation() { @Override public int operate(int a, int b) { return a + b; } }; // Using a lambda expression MathOperation addition = (int a, int b) -> a + b;
Vamos examinar mais de perto o que está acontecendo no código acima e como usamos expressões lambda:
main.java
1234567891011121314package com.example; // Functional interface with a single abstract method interface MyMathOperation { int operate(int a, int b); } public class Main { public static void main(String[] args) { // Using a lambda expression to implement the interface MyMathOperation addition = (a, b) -> a + b; System.out.println("Sum: " + addition.operate(5, 3)); } }
Explicação:
- Criada uma interface funcional
MyMathOperation
com um único método abstratooperate
. - 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:
main.java
12345678910111213package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); stream.map(e -> e.toUpperCase()).toList(); } }
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:
main.java
1234567891011121314package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.toUpperCase()).toList(); System.out.println(list); } }
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:
main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
- 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:
main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); stream = stream.filter(e -> e.length() > 5); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
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:
main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); List<String> list = strings.stream() .filter(e -> e.length() > 5) .map(e -> e.substring(1, 4)) .toList(); System.out.println("Modified list: " + list); } }
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:
main.java
123456789101112131415161718192021222324package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
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:
main.java
12345678910111213141516171819202122232425262728293031323334package com.example; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); Map<String, String> usersKeyValue = new HashMap<>(); for (int i = 0; i < list.size() - 1; i+=2) { String name = list.get(i); String surname = list.get(i + 1); usersKeyValue.put(surname, name); } System.out.println("Map with surnames as keys and names as values: " + usersKeyValue); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
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:
list.stream().distinct().toList()
-
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 primeirosn
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étodolimit(n)
, que geralmente limita o número de elementos no fluxo. Mesmo que criemos um fluxo com 1000 elementos e depois usemoslimit(200)
, o fluxo conterá apenas os primeiros 200 elementos.
main.java
123456789101112package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example = example.stream().skip(3).limit(5).toList(); System.out.println("List: " + example); } }
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 complexacollect(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 interfaceCollectors
. Esta interface possui métodos comotoList()
,toSet()
etoCollection()
. Por exemplo:
main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Set<Integer> integerSet = example.stream().collect(Collectors.toSet()); System.out.println("List: " + example); System.out.println("Set: " + integerSet); } }
O método forEach()
recebe uma expressão lambda e realiza uma ação específica para cada elemento no stream.
Por exemplo:
main.java
1234567891011package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example.stream().forEach(e -> System.out.println(e + 1)); } }
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?
Obrigado pelo seu feedback!
Pergunte à IA
Pergunte à IA
Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo
What are some examples of using the Stream API in Java?
Can you explain more about lambda expressions and how they work?
How do intermediate and terminal operations differ in practice?
Awesome!
Completion rate improved to 4
API de Transmissão
Deslize para mostrar o menu
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.
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()
eparallelStream()
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:
main.java
12List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream();
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:
example.java
123(parameters) -> expression // or (parameters) -> { statements; }
-
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:
example.java
12345678910// Traditional way MathOperation addition = new MathOperation() { @Override public int operate(int a, int b) { return a + b; } }; // Using a lambda expression MathOperation addition = (int a, int b) -> a + b;
Vamos examinar mais de perto o que está acontecendo no código acima e como usamos expressões lambda:
main.java
1234567891011121314package com.example; // Functional interface with a single abstract method interface MyMathOperation { int operate(int a, int b); } public class Main { public static void main(String[] args) { // Using a lambda expression to implement the interface MyMathOperation addition = (a, b) -> a + b; System.out.println("Sum: " + addition.operate(5, 3)); } }
Explicação:
- Criada uma interface funcional
MyMathOperation
com um único método abstratooperate
. - 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:
main.java
12345678910111213package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); stream.map(e -> e.toUpperCase()).toList(); } }
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:
main.java
1234567891011121314package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.toUpperCase()).toList(); System.out.println(list); } }
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:
main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
- 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:
main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); stream = stream.filter(e -> e.length() > 5); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
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:
main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); List<String> list = strings.stream() .filter(e -> e.length() > 5) .map(e -> e.substring(1, 4)) .toList(); System.out.println("Modified list: " + list); } }
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:
main.java
123456789101112131415161718192021222324package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
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:
main.java
12345678910111213141516171819202122232425262728293031323334package com.example; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); Map<String, String> usersKeyValue = new HashMap<>(); for (int i = 0; i < list.size() - 1; i+=2) { String name = list.get(i); String surname = list.get(i + 1); usersKeyValue.put(surname, name); } System.out.println("Map with surnames as keys and names as values: " + usersKeyValue); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
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:
list.stream().distinct().toList()
-
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 primeirosn
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étodolimit(n)
, que geralmente limita o número de elementos no fluxo. Mesmo que criemos um fluxo com 1000 elementos e depois usemoslimit(200)
, o fluxo conterá apenas os primeiros 200 elementos.
main.java
123456789101112package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example = example.stream().skip(3).limit(5).toList(); System.out.println("List: " + example); } }
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 complexacollect(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 interfaceCollectors
. Esta interface possui métodos comotoList()
,toSet()
etoCollection()
. Por exemplo:
main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Set<Integer> integerSet = example.stream().collect(Collectors.toSet()); System.out.println("List: " + example); System.out.println("Set: " + integerSet); } }
O método forEach()
recebe uma expressão lambda e realiza uma ação específica para cada elemento no stream.
Por exemplo:
main.java
1234567891011package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example.stream().forEach(e -> System.out.println(e + 1)); } }
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?
Obrigado pelo seu feedback!