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.

Obtener datos

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).

Recolectar elementos

Búsqueda de elementos

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); 

Estructuras mutables: lista o maps

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

⚠️ **GitHub.com Fallback** ⚠️