Java 8 Stream

Java 8 新特性Novedades de Java 8


La API de Java 8 añadió una nueva abstracción llamada Stream, que te permite procesar datos de forma declarativa.

Stream proporciona una abstracción de alto nivel para las operaciones y expresiones de las colecciones de Java, utilizando una forma intuitiva similar a consultar datos de una base de datos con sentencias SQL.

La API de Stream puede aumentar enormemente la productividad de los programadores de Java, permitiéndoles escribir código eficiente, limpio y conciso.

Este estilo considera la colección de elementos a procesar como un flujo, que se transmite en una tubería y puede procesarse en los nodos de la tubería, como filtrar, ordenar, agregar, etc.

El flujo de elementos pasa por operaciones intermedias (intermediate operation) en la tubería y, finalmente, una operación terminal (terminal operation) produce el resultado del procesamiento anterior.

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

El proceso anterior se convierte al siguiente código Java:

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

¿Qué es Stream?

Stream (flujo) es una cola de elementos de una fuente de datos y admite operaciones de agregación.

  • Los elementos son objetos de un tipo específico que forman una cola. Stream en Java no almacena elementos, sino que los calcula bajo demanda.
  • Fuente de datosEl origen del flujo. Puede ser una colección, un array, un canal de E/S, un generador, etc.
  • Operaciones de agregaciónOperaciones similares a sentencias SQL, como filter, map, reduce, find, match, sorted, etc.

A diferencia de las operaciones de Collection anteriores, las operaciones de Stream tienen dos características básicas más:

  • Pipelining: Las operaciones intermedias devuelven el propio objeto de flujo. De esta manera, múltiples operaciones pueden encadenarse en una tubería, como un estilo fluido (fluent style). Esto permite optimizar las operaciones, como la ejecución diferida (laziness) y el cortocircuito (short-circuiting).
  • Iteración interna: Anteriormente, el recorrido de colecciones se realizaba mediante Iterator o For-Each, iterando explícitamente fuera de la colección, lo que se llama iteración externa. Stream proporciona una forma de iteración interna, implementada mediante el patrón Visitor.

Generar flujos

En Java 8, las interfaces de colección tienen dos métodos para generar flujos:

  • stream()− Crear un flujo secuencial para la colección.

  • parallelStream()− Crear un flujo paralelo para la colección.

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach

Stream proporciona el nuevo método 'forEach' para iterar sobre cada dato del flujo. El siguiente fragmento de código usa forEach para generar 10 números aleatorios:

Random random = new Random(); random.ints().limit(10).forEach(System.out::println);

map

El método map se utiliza para mapear cada elemento al resultado correspondiente. El siguiente fragmento de código usa map para generar los cuadrados de los elementos:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //Obtener los cuadrados correspondientes List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter

El método filter se utiliza para filtrar elementos según las condiciones establecidas. El siguiente fragmento de código usa filter para filtrar cadenas vacías:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //Obtener el número de cadenas vacías long count = strings.stream().filter(string -> string.isEmpty()).count();

limit

El método limit se utiliza para obtener un flujo con un número específico de elementos. El siguiente fragmento de código usa limit para imprimir 10 datos:

Random random = new Random(); random.ints().limit(10).forEach(System.out::println);

sorted

El método sorted se utiliza para ordenar el flujo. El siguiente fragmento de código usa sorted para ordenar los 10 números aleatorios generados:

Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println);

Programas paralelos

parallelStream es un método alternativo para el procesamiento paralelo de flujos. En el siguiente ejemplo, usamos parallelStream para generar el número de cadenas vacías:

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //Obtener el número de cadenas vacías long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

Podemos cambiar fácilmente entre ejecución secuencial y paralela.


Collectors

La clase Collectors implementa muchas operaciones de reducción, como convertir flujos en colecciones y agregar elementos. Collectors se puede usar para devolver listas o cadenas:

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("Filtrar listas:" + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("Unir cadenas:" + mergedString);

Estadísticas

Además, algunos recolectores que generan resultados estadísticos también son muy útiles. Se utilizan principalmente en tipos básicos como int, double, long, y pueden usarse para producir resultados estadísticos como los siguientes.

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("El número más grande de la lista:" + stats.getMax()); System.out.println("El número más pequeño de la lista:" + stats.getMin()); System.out.println("La suma de todos los números:" + stats.getSum()); System.out.println("El promedio:" + stats.getAverage());

Ejemplo completo de Stream

Coloque el siguiente código en el archivo Java8Tester.java:

Archivo 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("El número de cadenas vacías es:" + count); count = getCountLength3UsingJava7(strings); System.out.println("El número de cadenas con longitud 3 es:" + count); //Eliminar cadenas vacías List<String> filtered = deleteEmptyStringsUsingJava7(strings); System.out.println("Lista filtrada:" + filtered); //Eliminar cadenas vacías y unirlas con comas String mergedString = getMergedStringUsingJava7(strings,", "); System.out.println("Cadena combinada:" + mergedString); List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //Obtener los cuadrados de los elementos de la lista List<Integer> squaresList = getSquares(numbers); System.out.println("Lista de cuadrados:" + squaresList); List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19); System.out.println("Lista:" +integers); System.out.println("El número más grande de la lista:" + getMax(integers)); System.out.println("El número más pequeño de la lista:" + getMin(integers)); System.out.println("La suma de todos los números:" + getSum(integers)); System.out.println("El promedio:" + getAverage(integers)); System.out.println("Números aleatorios:"); //Generar 10 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("El número de cadenas vacías es:" + count); count = strings.stream().filter(string -> string.length() == 3).count(); System.out.println("El número de cadenas con longitud 3 es:" + 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("Squares List: " + squaresList); System.out.println("Lista:" +integers); IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics(); System.out.println("El número más grande de la lista:" + stats.getMax()); System.out.println("El número más pequeño de la lista:" + stats.getMin()); System.out.println("La suma de todos los números:" + stats.getSum()); System.out.println("El promedio:" + 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("El número de cadenas vacías es:" + 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(); } }

Al ejecutar el script anterior, el resultado es:

$ javac Java8Tester.java 
$ java Java8Tester
使用 Java 7: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
平方数列表: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9
随机数: 
-393170844
-963842252
447036679
-1043163142
-881079698
221586850
-1101570113
576190039
-1045184578
1647841045
使用 Java 8: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符串数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9.444444444444445
随机数: 
-1743813696
-1301974944
-1299484995
-779981186
136544902
555792023
1243315896
1264920849
1472077135
1706423674
空字符串的数量为: 2

Java 8 新特性Novedades de Java 8

Otras extensiones