Java 8 Streams: Abordagem Declarativa para Processamento de Dados
Stream é uma nova camada abstrata introduzida no Java 8. Usando stream, você pode processar dados de forma declarativa, similar a instruções SQL. Por exemplo, considere a instrução SQL:
SELECT max(salary), employee_id, employee_name FROM Employee
A expressão SQL acima retorna automaticamente detalhes do funcionário com maior salário, sem computação manual do desenvolvedor. Com Collections framework no Java, o desenvolvedor precisa usar loops e verificações repetidas. Outra preocupação é eficiência; com processadores multi-core disponíveis, escrever código de processamento paralelo pode ser propenso a erros.
Para resolver isso, Java 8 introduziu streams, permitindo processar dados declarativamente e aproveitar arquitetura multi-core sem escrever código paralelo específico.
O que é Stream?
Stream representa uma sequência de objetos de uma fonte, suportando operações agregadas. Características:
- Sequência de elementos - Fornece conjunto de elementos de tipo específico de forma sequencial. Computa elementos sob demanda. Nunca armazena elementos.
- Fonte - Recebe Collections, Arrays ou recursos I/O como fonte.
- Operações agregadas - Suporta filter, map, limit, reduce, find, match, etc.
- Pipelining - A maioria das operações retorna stream para encadeamento. São operações intermediárias.
collect()é operação terminal, normalmente no final do pipeline. - Iteraçãos automáticas - Operações de stream iteram internamente sobre elementos da fonte, ao contrário de Collections onde iteração explícita é necessária.
Gerando Streams
Com Java 8, interface Collection tem dois métodos para gerar Stream:
stream()- Retorna stream sequencial considerando collection como fonte.parallelStream()- Retorna stream paralela considerando collection como fonte.
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
forEach
Stream fornece método forEach para iterar cada elemento. Exemplo: imprimir 10 números aleatórios:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
Método map mapeia cada elemento para seu resultado correspondente. Exemplo: quadrados únicos:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = numbers.stream().map(i -> i*i).distinct().collect(Collectors.toList());
filter
Método filter elimina elementos baseados em critério. Exemplo: contar strings vazias:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
int count = strings.stream().filter(s -> s.isEmpty()).count();
limit
Método limit reduz tamanho do stream. Exemplo: 10 números aleatórios:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
sorted
Método sorted ordena o stream. Exemplo: 10 números aleatórios ordenados:
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
Processamento Paralelo
parallelStream é alternativa de stream para processamento paralelo. Exemplo: contar strings vazias:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
long count = strings.parallelStream().filter(s -> s.isEmpty()).count();
É muito fácil alternar entre streams sequenciais e paralelas.
Collectors
Collectors combinam resultado do processamento nos elementos de um stream. Podem retornar lista ou string.
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
String mergedString = strings.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining(", "));
Estatísticas
Com Java 8, collectors de estatísticas calculam todas as estatísticas durante processamento de stream.
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("Highest: " + stats.getMax());
System.out.println("Lowest: " + stats.getMin());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
Exemplo Completo: Java 7 vs Java 8
Java8Tester.java
import java.util.*;
import java.util.stream.Collectors;
import java.util.IntSummaryStatistics;
public class Java8Tester {
public static void main(String args[]) {
System.out.println("Using Java 7: ");
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
System.out.println("List: " + strings);
long count = getCountEmptyStringUsingJava7(strings);
System.out.println("Empty Strings: " + count);
count = getCountLength3UsingJava7(strings);
System.out.println("Strings of length 3: " + count);
List<String> filtered = deleteEmptyStringsUsingJava7(strings);
System.out.println("Filtered List: " + filtered);
String mergedString = getMergedStringUsingJava7(strings, ", ");
System.out.println("Merged String: " + mergedString);
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = getSquares(numbers);
System.out.println("Squares List: " + squaresList);
List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);
System.out.println("List: " + integers);
System.out.println("Highest: " + getMax(integers));
System.out.println("Lowest: " + getMin(integers));
System.out.println("Sum: " + getSum(integers));
System.out.println("Average: " + getAverage(integers));
System.out.println("Random Numbers: ");
Random random = new Random();
for (int i = 0; i < 10; i++) System.out.println(random.nextInt());
System.out.println("\nUsing Java 8: ");
System.out.println("List: " + strings);
count = strings.stream().filter(s -> s.isEmpty()).count();
System.out.println("Empty Strings: " + count);
count = strings.stream().filter(s -> s.length() == 3).count();
System.out.println("Strings of length 3: " + count);
filtered = strings.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered List: " + filtered);
mergedString = strings.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);
squaresList = numbers.stream().map(i -> i*i).distinct().collect(Collectors.toList());
System.out.println("Squares List: " + squaresList);
System.out.println("List: " + integers);
IntSummaryStatistics stats = integers.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("Highest: " + stats.getMax());
System.out.println("Lowest: " + stats.getMin());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
System.out.println("Random Numbers: ");
random.ints().limit(10).sorted().forEach(System.out::println);
count = strings.parallelStream().filter(s -> s.isEmpty()).count();
System.out.println("Empty Strings (parallel): " + count);
}
// Métodos Java 7 (omitidos por brevidade - versões imperativas com loops)
private static int getCountEmptyStringUsingJava7(List<String> strings) { /* ... */ return 0; }
private static int getCountLength3UsingJava7(List<String> strings) { /* ... */ return 0; }
private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) { /* ... */ return null; }
private static String getMergedStringUsingJava7(List<String> strings, String sep) { /* ... */ return ""; }
private static List<Integer> getSquares(List<Integer> numbers) { /* ... */ return null; }
private static int getMax(List<Integer> numbers) { /* ... */ return 0; }
private static int getMin(List<Integer> numbers) { /* ... */ return 0; }
private static int getSum(List<Integer> numbers) { /* ... */ return 0; }
private static int getAverage(List<Integer> numbers) { /* ... */ return 0; }
}
O exemplo completo demonstra como Streams tornam o código mais conciso, legível e paralelo sem esforço extra.
Java
- Dominando Operadores Java: Tipos, Sintaxe e Exemplos Práticos
- Comentários em Java: Tipos, Uso e Boas Práticas
- Dominando o Loop for Aprimorado em Java: Sintaxe, Exemplos e Boas Práticas
- Dominando Strings em Java: Criação, Métodos e Boas Práticas
- Dominando Interfaces em Java: Conceitos, Implementação e Boas Práticas
- Dominando Classes Internas Anônimas em Java: Definição, Sintaxe e Exemplos Práticos
- Dominando Try-With-Resources em Java: Gerenciamento Automático de Recursos Explicado
- Anotações em Java Explicadas: Tipos, Posicionamento e Exemplos Práticos
- Asserções em Java: Guia Prático para Detectar Bugs e Garantir Confiabilidade do Código
- Gerar Números Aleatórios em Java: Guia Prático com Random e Math.random