Uso básico de Java 8: Stream y Lambdas.

1
16411

En este tutorial vamos a ver ejemplos hechos con Programación imperativa vs. Programación funcional y qué ventajas nos aporta.

Índice de contenidos

1. Introducción

Esta es la primera parte de un tutorial dedicado a Java 8. En esta primera parte elaboraré ejemplos entre Programación imperativa y Programación funcional para en las próximas partes realizar microbenchmarking a mano y con JMH, comparando el rendimiento de cada uno de los ejemplos.

Java 8 nos abre la puerta a la programación funcional con las expresiones lambda, también llamadas funciones anónimas, y la API Stream. Este tutorial no pretende entrar en profundidad en el tema
si no ser un pequeño acercamiento a estas nuevas funcionalidades comparadas con la forma de realizarlas en programación imperativa. Para entrar más en profundidad, tenéis los siguientes
tutoriales:

Si quieres conocer todas las novedades de java 8, pincha en el siguiente enlace: Novedades Java8

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 17′ (3 Ghz Intel Core 2 Duo, 8GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11
  • Entorno de desarrollo: IntelliJ Idea 2016.2
  • JavaSE build 1.8.0_77-b03
  • Maven 3.3.9

3. Proyecto

Como el objetivo de este tutorial es centrarnos en el uso de Stream y Lambdas, he creado el proyecto más sencillo que se me ha ocurrido. Un carrito de la compra que nos devuelve el número de productos
y el precio total de ellos. A continuación dejo el código:

CarritoDeLaCompra.java

CarritoBuilder.java

También, como buenos seguidores de la filosofía TDD que somos en Autentia, he creado los test que me permitan probar que lo que realizo es totalmente funcional.
Os dejo también el código de los test:

CarritoDeLaCompraTest.java

Con el objetivo de en los siguientes tutoriales realizar pruebas de rendimiento, en vez de modificar los métodos voy a crear nuevos. AVISO: los tiempos de ejecución de los test que aparecen en esta primera parte no son representativos.

4. Lambdas y Stream

Empecemos con nuestras primeras líneas de programación funcional. Para empezar, creamos un nuevo método, en mi caso el método calcularPrecioTotalLambda() cuyo código es el siguiente:

calcularPrecioTotalLambda()

Como podéis observar, hacemos uso del stream, el cual nos recogerá cada objeto de la Collection precios (this.precios.stream()) y lo mapeará a Integers (mapToInt()).
La lambda que pasamos por parámetro a mapToInt nos dará un warning.


Captura de pantalla 2016-05-10 a las 15.26.05

Si pulsamos el atajo [Alt + Enter] la solución será sustituir la lambda por una referencia a método. De tal manera que el código quedaría así:

calcularPrecioTotalRefMethod()

Las dos formas hacen lo mismo: sacar de cada Integer su valor. La diferencia es la llamada que hacemos. En el primer caso (i -> i.intValue()) llamamos al método intValue de cada Integer.
En el segundo caso (Integer::intValue) hacemos uso del método a través de una referencia (incluidas en Java 8). Si tuviera que decantarme por una forma, quizás la más clara sea
i -> i.intValue(), pero sin duda la más correcta es Integer::intValue.

Ahora creamos los test de estos dos nuevos métodos y vemos que todo funciona:

CarritoDeLaCompraTest.java

Captura de pantalla 2016-05-13 a las 15.56.07

5. Filter

Hasta aquí ha sido un primer contacto muy básico con los Streams, las Lambdas y las Referencias a métodos. Pretendía dar a entender las similitudes entre un bucle for y un stream para ahora
entrar más en detalle en las posibilidades que nos ofrece Java 8.

Obviamente con las opciones que tenemos ahora en CarritoDeLaCompra poco podríamos hacer, por lo que, debemos extender funcionalidades. La primera va a ser un detector de descuentos
que comprueba si algún precio es mayor o igual que el que pasamos por parámetro y, por cada coincidencia, descuenta un 5% :

calcularDescuentoTotal()

El método de testeo para este nuevo método:

Test


Captura de pantalla 2016-05-12 a las 19.17.38

Ahora, vamos a hacer un nuevo método que use Stream y Lambda:

calcularDescuentoTotalLambda()

Volvemos a tener un ejemplo para comparar entre prog. imperativa y prog. funcional, ahora tratemos de entenderlo. Como podéis observar, he añadido un nuevo método llamado filter().
Dicho método recibe como parámetro un predicado. Para entenderlo, no hay más que traducirlo
al español: filtro.

Nos va a servir de filtro con la condición que nosotros le pongamos, aplicando el siguiente método únicamente sobre los valores que la cumplan. En mi caso, la
condición es que cada valor debe ser mayor o igual a la cantidad pasada por parámetro para que se cuente. Al final, tendremos la cuenta de todos los precios que tienen descuento.
Una vez contados aplicamos unas matemáticas básicas para sacar el descuento total.

Ahora creamos el test y lo ejecutamos para comprobar si es correcto.

shouldCalculateTotalDiscountLambda()


Captura de pantalla 2016-05-12 a las 19.38.17

6. anyMatch()

Para no repetir números muy extensos, he creado dos constantes que usaré a lo largo de los próximos test.

Tambien he añadido un método a CarritoBuilder llamado addMultiple() que me permita añadir mucho valores para dejar el número negativo en mitad del array completo.

addMultiple()

Vamos a seguir añadiendo funcionalidades a nuestro carrito. El nuevo método que he creado si detecta un valor erróneo devuelve true. Los valores erróneos serán
todos aquellos valores que lleguen como un número negativo. A continuación muestro el código del nuevo método:

detectarError()

Como es evidente, ahora necesitamos la clase de test para este método:

shouldDetectErrorAndReturnTrueWhenAPriceIsNegative()

Captura de pantalla 2016-05-24 a las 9.48.40

A continuación dejo el código del ejemplo usando un nuevo método: anyMatch()

detectarErrorAnyMatch()

anyMatch(), al igual que filter(), recibe un predicado. En este caso devuelve true si encuentra algún precio negativo.

Creamos el test y lo ejecutamos para comprobar que funciona correctamente:

shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeAnyMatch()

Captura de pantalla 2016-05-24 a las 9.52.51

7. findAny() e isPresent()

Para estos nuevos métodos voy a usar el mismo ejemplo de antes. Más adelante veremos si realmente existen diferencias más allá de la sintaxis de cada forma. Tomamos como referencia
el detectarError() y realizamos el siguiente método:

En este ejemplo hacemos uso de los métodos findAny() e isPresent(). Si traducimos al español quedaría como «…y encuentra alguno. ¿Está presente?». Es decir, findAny() nos devuelve
un Optional cuando se cumple la condición de filter(). Este Optional tiene como método isPresent() el cual si encuentra una coincidencia devolverá true. Hay que aclarar que esta forma recorre todo el stream.

Habría una forma de evitar que recorra todo el stream y es usando el método findFirst(), es decir, «encuentra el primero».

shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeNumberFindAny()

shouldDetectErrorAndReturnTrueWhenAPriceIsNegativeNumberFindFirst()

Ejecutamos los test para comprobar su correcto funcionamiento.

Captura de pantalla 2016-05-24 a las 10.51.25

Existen muchas otras funcionalidades pero el objetivo de este post no es explicarlas todas.

8. parallelStreams()

En este último punto, voy a hacer uso de parallelStreams. Para conocer los parallelStream os recomiendo dos enlaces:

Cogiendo los tres ejemplos de detectar precios nulos, he creado los siguientes métodos usando un parallelStream:

detectarErrorXXXParallel()

shouldDetectErrorAnThrowRuntimeExceptionWhenAPriceIsNegativeXXXParallel()

Lanzamos los test por separado:

Captura de pantalla 2016-05-24 a las 10.56.15

Captura de pantalla 2016-05-24 a las 10.56.48

Captura de pantalla 2016-05-24 a las 10.57.15

9. Conclusión

Este tutorial no lo he enfocado en aprender con exactitud todos los entresijos de Java 8, sino más bien en servir de primer contacto a todos aquellos que quieran empezar a usar la programación funcional. Lo cierto es que hay otros lenguajes como Scala que ofrecen una programación funcional bastante más avanzada, pero como dije al principio, Java8 solo ha sido la primera puerta hacia la programación funcional.

En la segunda parte de este tutorial realizaré las pruebas de Benchmarking comparando el rendimiento de cada uno de los métodos que he creado para comprobar en diferentes situaciones cuales son las ventajas y desventajas de la Programación imperativa vs Programación funcional.

1 Comentario

  1. […] es la segunda parte del tutorial sobre Java 8 que he realizado. En esta segunda parte me centraré en comprobar cuales son las ventajas y […]

Dejar respuesta

Please enter your comment!
Please enter your name here