Operaciones Intermedias en Curso de Programación Funcional Con Java SE
Operaciones Intermedias en Curso de Programación Funcional Con Java SE
Artículo
Operaciones Intermedias
sierisimo 21 de Febrero de 2020
Operaciones intermedias
¿Qué son?
Se le dice operación intermedia a toda operación dentro de un Stream que como resultado
devuelva un nuevo Stream . Es decir, tras invocar una operación intermedia con un cierto tipo
de dato, obtendremos como resultado un nuevo Stream conteniendo los datos ya modi cados.
El Stream que recibe la operación intermedia pasa a ser “consumido” posterior a la invocación
de la operación, quedando inutilizable para posteriores operaciones. Si decidimos usar el
Collectors
Stream para algún otro tipo de operaciones tendremos un IllegalStateException .
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 1/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
El ejemplo anterior puede ser reescrito usando chaining. Sin embargo, la utilidad de este
ejemplo es demostrar que las operaciones intermedias generan un nuevo Stream .
Operaciones disponibles
La interfaz Stream cuenta con un grupo de operaciones intermedias. A lo largo de esta lectura
explicaremos cada una de ellas y trataremos de aproximar su funcionalidad. Cada operación
tiene implementaciones distintas según la implementación de Stream , en nuestro caso,
filter(…)
Collectors
map(…)
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 2/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
flatMap(…)
distinct(…)
limit(…)
peek(…)
skip(…)
sorted(…)
Analicemos qué hace cada una de ellas y hagamos código que se aproxime a lo que hacen
internamente.
lter
Algunas cosas que podemos deducir únicamente viendo los elementos de la operación son:
La operación trabaja sobre un Stream y nos devuelve un nuevo Stream del mismo tipo ( T )
Sin embargo, el Predicate que recibe como parámetro trabaja con elementos de tipo T y
cualquier elemento siempre que sea un subtipo de T . Esto quiere decir que si tenemos la
Collectors
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 3/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
Lo interesante radica en la condición que usamos en nuestra lambda, con ella determinamos si
un elemento debe permanecer o no en el Stream resultante. En la lectura anterior hicimos una
aproximación de la operación filter :
return filteredData.stream();
} Collectors
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 4/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
filter se encarga de iterar cada elemento del Stream y evaluar con el Predicate si el elemento
debe estar o no en el Stream resultante. Si nuestro Predicate es sencillo y no incluye ningún
ciclo o llamadas a otras funciones que puedan tener ciclos, la complejidad del tiempo es de
O(n) , lo cual hace que el ltrado sea bastante rápido.
Usos comunes de filter es limpiar un Stream de datos que no cumplan un cierto criterio.
Como ejemplo podrías pensar en un Stream de transacciones bancarias, mantener el Stream
solo aquellas que superen un cierto monto para mandarlas a auditoria, de un grupo de
cali caciones de alumnos ltrar únicamente por aquellos que aprobaron con una cali cación
superior a 6, de un grupo de objetos JSON conservar aquellos que tengan una propiedad en
especi co, etc.
Entre mas sencilla sea la condición de ltrado, más legible sera el código. Te recomiendo que, si
tienes más de una condición de ltrado, no le temas a usar varias veces filter :
Tu código sera más legible y las razones de por qué estás aplicando cada ltro tendrán más
sentido. Como algo adicional podrías mover esta lógica a funciones individuales en caso de que
quieras hacer más legible el código, tener más facilidad de escribir pruebas o utilices en más de
Collectors
un lugar la misma lógica para algunas lambdas:
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 5/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
courses.filter(Predicates::isAJavaCourse)
.filter(Predicates::hasEnoughDuration)
.filter(Predicates::hasSinuheAsInstructor);
// La lógica es la misma:
public final class Predicates {
public static final boolean isAJavaCourse(Course course){
return course.getName().contains("Java");
}
}
map
Los detalles a resaltar son muy similares a los de filter , pero la diferencia clave está en T y R.
Estos generics nos dicen que map va a tomar un tipo de dato T , cualquiera que sea, le aplicara la
Es algo similar a lo que hacías en la secundaria al convertir en una tabla datos, para cada x
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 6/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
cada elemento en el Stream inicial aplicando la Function que le pases como lambda para
generar un nuevo elemento y hacerlo parte del Stream resultante:
O, puesto de otra forma, por cada DatabaseID en el Stream inicial, al aplicar map genera un
User :
Esto resulta bastante practico cuando queremos hacer alguna conversión de datos y realmente
no nos interesa el dato completo (solo partes de él) o si queremos convertir a un dato complejo
partiendo de un dato base.
return mappedData.stream();
}
La operación map parece simple ya vista de esta manera. Sin embargo, por dentro de las
diferentes implementaciones de Stream hace varias validaciones y optimizaciones para que la
operación pueda ser invocada en paralelo, para prevenir algunos errores de conversión de
tipos y hacer que sea mas rápida que nuestra versión con un for .
atMap
En ocasiones no podremos evitar encontrarnos con streams del tipo Stream> , donde tenemos
Este tipo de streams es bastante común y puede pasarte por multiples motivos. Se puede tornar
difícil operar el Stream inicial si queremos aplicar alguna operación a cada uno de los
elementos en cada una de las listas.
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 8/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
26
flatMap tiene la siguiente forma:
27
Stream flatMap(Functionsuper T, ? extends Stream> mapper)
28
Lo interesante es que el resultado de la función mapper debe ser un Stream . Stream usará el
29
resultado de mapper para “acumular” elementos en un Stream desde otro Stream . Puede sonar
31
//Tenemos esta clase:
public class PlatziStudent {
32 private boolean emailSubscribed;
private List emails;
// Despues, queremos enviarle un correo a todos los usuarios pero… solo nos interesa
obtener su correo para notificarlos:
Stream allEmailsToNotify =
platziStudents.filter(PlatziStudent::isEmailSubscribed) //Primero
evitamos enviar correos a quienes no estén subscritos
.flatMap(student -> student.getEmails().stream()); //
La lambda genera un nuevo Stream de la lista de emails de cada studiante.
sendMonthlyEmails(allEmailsToNotify);
//El Stream final solo es un Stream de emails, sin mas detalles ni información adicional.
flatMap es una manera en la que podemos depurar datos de información adicional que no sea
necesaria.
distinct
Stream distinct()
Lo que hace es comparar cada elemento del Stream contra el resto usando el método equals .
De esta manera, evita que el Stream contenga elementos duplicados. La operación, al ser
intermedia, retorna un nuevo Stream donde los elementos son únicos. Recuerda que para
garantizar esto es recomendable que sobrescribas el método equals en tus clases que
Collectors
representen datos.
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 10/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
limit
La operación limit recibe un long que determina cuántos elementos del Stream original seran
preservados. Si el número es mayor a la cantidad inicial de elementos en el Stream ,
básicamente, todos los elementos seguirán en el Stream . Un detalle interesante es que algunas
performance.
La operación asegura que los elementos en el Stream resultante serán los primeros en
aparecer en el Stream . Es por ello que la operación es ligera cuando el Stream es secuencial o se
usó la operación unordered() (no disponible en todos los Streams , ya que la operación
Como reto adicional, crea el código para representar lo que hace la operación limit .
peek
peek funciona como una lupa, como un momento de observación de lo que está pasando en el
Stream . Lo que hace esta operación es tomar un Consumer , pasar los datos conforme van
Collectors
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 11/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
estando presentes en el Stream y generar un nuevo Stream idéntico para poder seguir
operando.
Usarlo puede ayudarnos a generar logs o registros de los datos del Stream , por ejemplo:
Stream serverConnections =
server.getConnectionsStream()
.peek(connection -> logConnection(connection, new Date()))
.filter(…)
.map(…)
//Otras operaciones…
skip
Esta operación es contraria a limit() . Mientras limit() reduce los elementos presentes en el
Stream a un numero especi co, skip descarta los primeros n elementos y genera un Stream
Esto es:
Collectors
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 12/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
Esto puede ser de utilidad si sabemos qué parte de la información puede ser descartable. Por
ejemplo, descartar la primera línea de un XML ( ), descartar metadatos de una foto, usuarios de
prueba al inicio de una base de datos, el intro de un video, etc.
sorted
El Stream resultante contiene todos los elementos pero ya ordenados, hacer un ordenamiento
tiene muchas ventajas, te recomiendo los cursos de algoritmos de Platzi para poder conocer a
mas detalle estas ventajas.
Conclusiones
Las operaciones intermedias nos permiten tener control sobre los streams y manipular sus
contenidos de manera sencilla sin preocuparnos realmente por cómo se realizan los cambios.
Recuerda que las operaciones intermedias tienen la funcionalidad de generar nuevos streams
que podremos dar como resultado para que otras partes del código los puedan utilizar.
Collectors
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 13/14
27/2/2020 Operaciones Intermedias en Curso de Programación Funcional con Java SE
que aquí listamos están presentes en la interfaz base, por lo que entender estas operaciones te
facilitara la vida en la mayoría de los usos de Stream .
Collectors
https://platzi.com/clases/1826-java-funcional/26878-operaciones-intermedias/ 14/14