Java 8 Streams: Enfoque Declarativo para Procesamiento de Datos
Stream es una nueva capa abstracta introducida en Java 8. Usando stream, puedes procesar datos de forma declarativa similar a sentencias SQL. Por ejemplo, considera la siguiente sentencia SQL.
SELECT max(salary), employee_id, employee_name FROM Employee
La expresión SQL anterior devuelve automáticamente los detalles del empleado con mayor salario, sin hacer ningún cómputo por parte del desarrollador. Usando el framework de colecciones en Java, un desarrollador tiene que usar bucles y hacer comprobaciones repetidas. Otra preocupación es la eficiencia; como los procesadores multi-núcleo están disponibles fácilmente, un desarrollador de Java tiene que escribir código de procesamiento paralelo que puede ser bastante propenso a errores.
Para resolver tales problemas, Java 8 introdujo el concepto de stream que permite al desarrollador procesar datos declarativamente y aprovechar la arquitectura multi-núcleo sin necesidad de escribir código específico para ello.
¿Qué es Stream?
Stream representa una secuencia de objetos desde una fuente, que soporta operaciones agregadas. A continuación se muestran las características de un Stream:
Secuencia de elementos - Un stream proporciona un conjunto de elementos de tipo específico de manera secuencial. Un stream obtiene/calcula elementos bajo demanda. Nunca almacena los elementos.
Fuente - Stream toma Colecciones, Arrays o recursos de E/S como fuente de entrada.
Operaciones agregadas - Stream soporta operaciones agregadas como filter, map, limit, reduce, find, match, etc.
Pipelining - La mayoría de las operaciones de stream devuelven el stream mismo para que su resultado pueda encadenarse. Estas operaciones se llaman operaciones intermedias y su función es tomar entrada, procesarlas y devolver salida al objetivo. El método
collect()es una operación terminal que normalmente está presente al final de la operación de pipelining para marcar el final del stream.Iteraciones automáticas - Las operaciones de stream hacen las iteraciones internamente sobre los elementos de la fuente proporcionados, en contraste con Collections donde se requiere iteración explícita.
Generar Streams
Con Java 8, la interfaz Collection tiene dos métodos para generar un Stream.
stream() - Devuelve un stream secuencial considerando la colección como su fuente.
parallelStream() - Devuelve un Stream paralelo considerando la colección como su fuente.
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
forEach
Stream ha proporcionado un nuevo método forEach para iterar cada elemento del stream. El siguiente segmento de código muestra cómo imprimir 10 números aleatorios usando forEach.
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
El método map se usa para mapear cada elemento a su resultado correspondiente. El siguiente segmento de código imprime cuadrados únicos de números usando map.
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// obtener lista de cuadrados únicos
List<Integer> squaresList = numbers.stream().map(i -> i*i).distinct().collect(Collectors.toList());
filter
El método filter se usa para eliminar elementos basándose en un criterio. El siguiente segmento de código imprime un conteo de cadenas vacías usando filter.
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// obtener conteo de cadena vacía
int count = strings.stream().filter(string -> string.isEmpty()).count();
limit
El método limit se usa para reducir el tamaño del stream. El siguiente segmento de código muestra cómo imprimir 10 números aleatorios usando limit.
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
sorted
El método sorted se usa para ordenar el stream. El siguiente segmento de código muestra cómo imprimir 10 números aleatorios en orden.
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
Procesamiento Paralelo
parallelStream es la alternativa de stream para procesamiento paralelo. Echa un vistazo al siguiente segmento de código que imprime un conteo de cadenas vacías usando parallelStream.
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// obtener conteo de cadena vacía
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
Es muy fácil cambiar entre streams secuenciales y paralelos.
Collectors
Los Collectors se usan para combinar el resultado del procesamiento en los elementos de un stream. Los Collectors pueden usarse para devolver una lista o una cadena.
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("Lista Filtrada: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Cadena Combinada: " + mergedString);
Estadísticas
Con Java 8, se introducen colectores de estadísticas para calcular todas las estadísticas cuando se está procesando el stream.
List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Número más alto en Lista: " + stats.getMax());
System.out.println("Número más bajo en Lista: " + stats.getMin());
System.out.println("Suma de todos los números: " + stats.getSum());
System.out.println("Promedio de todos los números: " + stats.getAverage());
Ejemplo de Stream
Crea el siguiente programa Java usando cualquier editor de tu elección en, digamos, C:\JAVA.
Java8Tester.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;
public class Java8Tester {
public static void main(String args[]) {
System.out.println("Usando Java 7: ");
// Contar cadenas vacías
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
System.out.println("Lista: " + strings);
long count = getCountEmptyStringUsingJava7(strings);
System.out.println("Cadenas Vacías: " + count);
count = getCountLength3UsingJava7(strings);
System.out.println("Cadenas de longitud 3: " + count);
// Eliminar cadena vacía
List<String> filtered = deleteEmptyStringsUsingJava7(strings);
System.out.println("Lista Filtrada: " + filtered);
// Eliminar cadena vacía y unir usando coma.
String mergedString = getMergedStringUsingJava7(strings,", ");
System.out.println("Cadena Combinada: " + mergedString);
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// obtener lista de cuadrados de números distintos
List<Integer> squaresList = getSquares(numbers);
System.out.println("Lista Cuadrados: " + squaresList);
List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);
System.out.println("Lista: " + integers);
System.out.println("Número más alto en Lista: " + getMax(integers));
System.out.println("Número más bajo en Lista: " + getMin(integers));
System.out.println("Suma de todos los números: " + getSum(integers));
System.out.println("Promedio de todos los números: " + getAverage(integers));
System.out.println("Números Aleatorios: ");
// imprimir diez números aleatorios
Random random = new Random();
for(int i = 0; i < 10; i++) {
System.out.println(random.nextInt());
}
System.out.println("Usando Java 8: ");
System.out.println("Lista: " + strings);
count = strings.stream().filter(string -> string.isEmpty()).count();
System.out.println("Cadenas Vacías: " + count);
count = strings.stream().filter(string -> string.length() == 3).count();
System.out.println("Cadenas de longitud 3: " + count);
filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("Lista Filtrada: " + filtered);
mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Cadena Combinada: " + mergedString);
squaresList = numbers.stream().map(i -> i*i).distinct().collect(Collectors.toList());
System.out.println("Lista Cuadrados: " + squaresList);
System.out.println("Lista: " + integers);
IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Número más alto en Lista: " + stats.getMax());
System.out.println("Número más bajo en Lista: " + stats.getMin());
System.out.println("Suma de todos los números: " + stats.getSum());
System.out.println("Promedio de todos los números: " + stats.getAverage());
System.out.println("Números Aleatorios: ");
random.ints().limit(10).sorted().forEach(System.out::println);
// procesamiento paralelo
count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("Cadenas Vacías: " + count);
}
private static int getCountEmptyStringUsingJava7(List<String> strings) {
int count = 0;
for(String string : strings) {
if(string.isEmpty()) {
count++;
}
}
return count;
}
private static int getCountLength3UsingJava7(List<String> strings) {
int count = 0;
for(String string : strings) {
if(string.length() == 3) {
count++;
}
}
return count;
}
private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
List<String> filteredList = new ArrayList<String>();
for(String string : strings) {
if(!string.isEmpty()) {
filteredList.add(string);
}
}
return filteredList;
}
private static String getMergedStringUsingJava7(List<String> strings, String separator) {
StringBuilder stringBuilder = new StringBuilder();
for(String string : strings) {
if(!string.isEmpty()) {
stringBuilder.append(string);
stringBuilder.append(separator);
}
}
String mergedString = stringBuilder.toString();
return mergedString.substring(0, mergedString.length()-2);
}
private static List<Integer> getSquares(List<Integer> numbers) {
List<Integer> squaresList = new ArrayList<Integer>();
for(Integer number : numbers) {
Integer square = new Integer(number.intValue() * number.intValue());
if(!squaresList.contains(square)) {
squaresList.add(square);
}
}
return squaresList;
}
private static int getMax(List<Integer> numbers) {
int max = numbers.get(0);
for(int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);
if(number.intValue() > max) {
max = number.intValue();
}
}
return max;
}
private static int getMin(List<Integer> numbers) {
int min = numbers.get(0);
for(int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);
if(number.intValue() < min) {
min = number.intValue();
}
}
return min;
}
private static int getSum(List numbers) {
int sum = (int)(numbers.get(0));
for(int i = 1; i < numbers.size(); i++) {
sum += (int)numbers.get(i);
}
return sum;
}
private static int getAverage(List<Integer> numbers) {
return getSum(numbers) / numbers.size();
}
}
Verificar el Resultado
Compila la clase usando el compilador javac de la siguiente manera:
C:\JAVA>javac Java8Tester.java
Ahora ejecuta Java8Tester de la siguiente manera:
C:\JAVA>java Java8Tester
Debería producir el siguiente resultado:
Usando Java 7:
Lista: [abc, , bc, efg, abcd, , jkl]
Cadenas Vacías: 2
Cadenas de longitud 3: 3
Lista Filtrada: [abc, bc, efg, abcd, jkl]
Cadena Combinada: abc, bc, efg, abcd, jkl
Lista Cuadrados: [9, 4, 49, 25]
Lista: [1, 2, 13, 4, 15, 6, 17, 8, 19]
Número más alto en Lista: 19
Número más bajo en Lista: 1
Suma de todos los números: 85
Promedio de todos los números: 9
Números Aleatorios:
-1279735475
903418352
-1133928044
-1571118911
628530462
18407523
-881538250
-718932165
270259229
421676854
Usando Java 8:
Lista: [abc, , bc, efg, abcd, , jkl]
Cadenas Vacías: 2
Cadenas de longitud 3: 3
Lista Filtrada: [abc, bc, efg, abcd, jkl]
Cadena Combinada: abc, bc, efg, abcd, jkl
Lista Cuadrados: [9, 4, 49, 25]
Lista: [1, 2, 13, 4, 15, 6, 17, 8, 19]
Número más alto en Lista: 19
Número más bajo en Lista: 1
Suma de todos los números: 85
Promedio de todos los números: 9.444444444444445
Números Aleatorios:
-1009474951
-551240647
-2484714
181614550
933444268
1227850416
1579250773
1627454872
1683033687
1798939493
Cadenas Vacías: 2
Java
- Dominar Operadores en Java: Tipos, Sintaxis y Ejemplos Prácticos
- Comentarios en Java: Tipos, Uso y Mejores Prácticas
- Dominar el Bucle for Mejorado en Java: Sintaxis, Ejemplos y Mejores Prácticas
- Dominar Strings en Java: Creación, Métodos y Mejores Prácticas
- Dominando interfaces en Java: Conceptos, implementación y mejores prácticas
- Dominando las clases internas anónimas en Java: Definición, sintaxis y ejemplos prácticos
- Dominando Try‑with‑Resources en Java: Gestión automática de recursos explicada
- Anotaciones en Java explicadas: Tipos, ubicación y ejemplos prácticos
- Aserciones en Java: Guía práctica para detectar bugs y asegurar fiabilidad del código
- Generar números aleatorios en Java: Guía práctica con Random y Math.random
- Mejoras en Java 9 Stream API: takeWhile, dropWhile, iterate y ofNullable