Operaciones Terminales - Tensho97/Aprende-a-Aprender GitHub Wiki
Provocan que todas las operaciones intermedias sean ejecutadas y pueden catalogarse así:
-
Operaciones terminales cuyo propósito es el consumo de los elementos del Stream, por ejemplo: +foreach(Consumer):void
-
Operaciones terminales que permiten obtener datos del Stream como: conteo, mínimo, máximo, búsqueda, y en general reducir el Stream a un valor.
-
Operaciones terminales que permiten recolectar los elementos de un Stream en estructuras mutables como por ejemplo listas.
A continuación revisaremos algunas de las operaciones terminales de las dos últimas categorías y ofreceremos ejemplos que ayuden a entender de una mejor manera su funcionamiento.
Entre las operaciones terminales que permiten obtener datos del Stream tenemos:
** +count():long**
Retorna la cantidad de elementos en el Stream . En el siguiente ejemplo, vemos como podemos obtener la cantidad de transacciones que existen en el Stream cuyo valor es mayor a dos mil:
List<Transaccion> trxs = ...
long count = trxs.stream()
.filter(t -> t.getValor() > 2000)
.count();
+max(Comparator):Optional
Retorna el elemento máximo del Stream basado en el comparador pasado por parámetro. Nótese que el retorno es de tipo java.util.Optional, clase nueva en Java SE 8 que representa un contenedor que puede o no tener un valor no-nulo.
El siguiente ejemplo hace uso de un nuevo método estático de la interface java.util.Comparator, el cual extrae el valor de un atributo de tipo java.lang.Double de los elementos del Stream y retorna un comparador que hace uso de dicho atributo para hacer la comparación:
List<Transaccion> trxs = ...
Optional<Transaccion> max =
trxs
.stream()
.max(Comparator.comparingDouble(Transaccion::getValor));
+min(Comparator):Optional
Retorna el elemento mínimo del Stream basado en el comparador pasado por parámetro. Nótese que el retorno es de tipo Optional (por si acaso el Stream se encuentra vacío).
También contamos con operaciones terminales que permiten realizar búsquedas entre los elementos de un Stream:
+allMatch(Predicate):boolean
Verifica si todos los elementos del Stream satisfacen el predicado pasado por parámetro. Si durante la verificación alguno no lo cumple entonces se detiene la verificación y retorna falso, es decir, no requiere procesar todo el Stream para producir el resultado. El siguiente ejemplo revisa que el tamaño de todas las cadenas de texto sea de al menos 4 caracteres. Nótese que el predicado lo hemos pasado como una expresión lambda:
List<String> palabras = Arrays.asList("Java", "Lambdas", "Stream", "API");
//Verifica si todas las palabras tienen un tamaño de 4 caracteres
boolean longitud = palabras.stream().allMatch(s -> s.length() >= 4);
+anyMatch(Predicate):boolean
Verifica si alguno de los elementos del Stream satisface el predicado pasado por parámetro. Si durante la verificación alguno lo cumple entonces se detiene la verificación y retorna verdadero, es decir, no requiere procesar todo el Stream para producir el resultado. En el siguiente ejemplo buscaremos un elemento en el Stream usando una expresión lambda como predicado:
List<String> palabras = Arrays.asList("Java", "Lambdas", "Stream", "API");
//Verifica si existe la cadena “lambda” dentro del Stream
boolean anymatch = palabras.stream()
.anyMatch(s -> s.equalsIgnoreCase("lambda"));
+noneMatch(Predicate):boolean
Contrario a +allMatch(Predicate):boolean, verifica si todos los elementos del Stream NO satisfacen el predicado pasado por parámetro. Si alguno SÍ lo cumple entonces se detiene la verificación y retorna falso, es decir, no requiere procesar todo el Stream para producir el resultado.
+findAny():Optional
Retorna algún elemento del Stream. Se recomienda usar este método cuando no se requiera un orden o cuando se esté usando Streams en paralelo. Nótese que el retorno es de tipo Optional (por si acaso el Stream se encuentra vacío).
//Obtiene alguno de los elementos del Stream
Optional<String> alguno = palabras.stream().findAny();
+findFirst():Optional
Retorna el primer elemento del Stream. No se recomienda usar este método cuando se tengan Streams en paralelo, debido a que obligaría a la API a sincronizar los hilos para no perder el orden de los elementos. Nótese que el retorno es de tipo Optional (por si acaso el Stream se encuentra vacío).
En general, si queremos reducir un Stream a un valor, podemos hacer uso del método mostrado a continuación, pero se aclara que existe una forma aún más genérica de reducir un Stream que se sale del alcance de este artículo:
+reduce(BinaryOperator):Optional
Realiza la reducción del Stream usando una función asociativa. Nótese que el retorno es de tipo Optional. En el siguiente ejemplo, obtenemos el mayor entero par que se encuentra en el Stream. La función que pasamos como parámetro usa el nuevo método estático +Integer.max(int, int):int
List<Integer> numeros = …
//Obtiene el posible número mayor par del Stream
Optional<Integer> opt = numeros.stream()
.filter(x -> x % 2 == 0)
.reduce(Integer::max);
+reduce(T, BinaryOperator):T
Realiza la reducción del Stream usando un valor inicial y una función asociativa. A continuación sumaremos todos los elementos del Stream usando el valor inicial 0, si el Stream se encuentra vacío, ese sería nuestro resultado:
List<Integer> numeros = …
//Obtiene la suma de los elementos del Stream
Integer suma = numeros
.stream()
.reduce(0, (x,y) -> x + y);
Para recolectar elementos en estructuras mutables como listas o maps es necesario utilizar collect, el cual es una operación terminal que permite recolectar los elementos de un Stream en dichas estructuras.
Java SE 8 introduce una nueva clase utilitaria llamada java.util.stream.Collectors que provee métodos estáticos que retornan los recolectores más usados. Dichos recolectores pueden ser agrupados en 3 tipos:
- Reducción y resumen: Reducen el Stream y permite obtener valores agregados como cantidad de elementos, sumas, promedios, etc.
- Agrupamiento: Agrupa elementos en un Map usando una “función de clasificación”, que permite establecer a qué grupo pertenece cada elemento.
- Particionamiento: Agrupamiento donde la función de clasificación es un predicado y por lo tanto agrupa los elementos en un Map de dos llaves: false y true
A continuación algunos recolectores que permiten obtener información agregada de un Stream:
Counting
Recolector que realiza la cuenta de elementos de un Stream. Es fácil pensar que ya existe una operación terminal que realiza la cuenta de elementos de un Stream (+count():long) y que no es necesario tener otra. Sin embargo, el tener esta operación como un recolector, nos permitirá usarla en conjunto con otros recolectores como veremos más adelante:
Import static java.util.stream.Collectors.*;
...
List<Integer> numeros = …
//Cantidad de elementos en el Stream
long cuenta = numeros
.stream()
.collect(counting());
MaxBy, MinBy
Estos recolectores permiten obtener el elemento máximo y mínimo respectivamente. Requieren que se les pase como parámetro un comparador que permita realizar las comparaciones. Su retorno es de tipo Optional por si acaso el Stream se encuentra vacío. En el siguiente ejemplo vamos a obtener el máximo entero del Stream de acuerdo a un comparador que compara enteros basado en su orden natural:
Import static java.util.stream.Collectors.*;
...
List<Integer> numeros = …
//Máximo elemento en el Stream de acuerdo al comparador
Optional<Integer> max = numeros.stream()
.collect(maxBy(Comparator.naturalOrder()));
SummingInt, SummingDouble, SummingLong
Recolectores que permiten realizar la suma de los elementos del Stream. Requieren que se les pase una función de tipo Supplier (es decir de las que retornan el valor requerido) para obtener el valor a sumar. En el siguiente ejemplo podemos ver que la función que pasamos como parámetro obtiene el valor entero del elemento que queremos sumar:
Import static java.util.stream.Collectors.*;
...
List<Integer> numeros = …
//Obtiene la suma los elementos del Stream como un entero
int suma = numeros
.stream()
.collect(summingInt(x -> x.intValue()));
AveragingInt, AveragingDoble, AveragingLong
Recolectores que permiten obtener el promedio de los elementos del Stream. Tambień requieren que se les pase una función de tipo Supplier (es decir de las que retornan el valor requerido) para obtener el valor a promediar.
SummarizingInt, SummarizingDouble, SummarizingLong
Recolectores que nos retornan instancias de clases que agrupan la información agregada del Stream. Tambień requieren que se les pase una función de tipo Supplier para obtener el valor a procesar. Las instancias que retornan son de tipo IntSummaryStatistics, DoubleSummaryStatistics o LongSummaryStatistics.
Import static java.util.stream.Collectors.*;
...
List<Integer> numeros = …
IntSummaryStatistics res = numeros
.stream()
.collect(summarizingInt(Integer::intValue));
Joining
Recolector que nos permite concatenar en una sola cadena, todas las cadenas retornadas por +toString():String de cada elemento del Stream. También es posible pasar como parámetro el delimitador que queremos usar entre elementos:
Import static java.util.stream.Collectors.*;
...
List<Integer> numeros = …
String csv = numeros.stream().map(Object::toString)
.collect(joining(", "));
Existe una versión genérica que permite crear todos los recolectores que hemos visto hasta el momento, pero que se sale del alcance de este artículo. Sin embargo, si el lector desea indagar más al respecto, existe un recolector que se puede obtener invocando Collectors.reducing.
Es posible crear recolectores que realicen operaciones de reducción de Streams si creamos instancias de la interface java.util.stream.Collector. Dejamos al lector la tarea de profundizar en este tema.
Autor: Richard