Java 8 provides two new ways to parameterize behavior called Lamda and method references.
In these slides I cover their integration with the current Java environment, and the fact that it's just sinctactic sugar, meaning that there is not really paradigm change in Java.
17. Lambdas
What is a lambda expression (in Java)?
A concise definition an anonymous implementation of a functional interface.
It’s composed by:
Parameters
Arrow
Body
18. Lambdas
Parameters
They define de parameters used inside the expression’s body.
Their types can be explicit or implicit (determined by the context).
Examples:
20. Lambdas
Body
Defines the behavior of the function.
Can be one expression or a block.
For one expression the return value is implicit.
For a block the return must be explicit.
29. Lambdas
Method references
A more legible way of defining a lambda expression that calls one method.
It’s composed by:
Target Method name
Double colon
32. Lambdas
Method name
The name of the method to execute.
Can be any method, including constructors.
The referenced method can have any number of parameters.
Daros las gracias por asistir a este curso.
Este es el primero de una serie de cursos sobre java 8.
Streams y Lambdas son lo más conocido pero hay mucho más.
En esta clase veremos dos temas muy relacionados: Parametrización de comportamiento y Lambdas.
En Java 7 los métodos son ciudadanos de segunda categoría, junto con clases e interfaces.
Para explicar este concepto y su necesidad, veremos primero que nada un ejemplo.
Dentro de los patrones que nos sirven para parametrizar comportamiento, utilizaremos el Strategy.
Tenemos la estrategia que representa un tipo de comportamiento.
Luego están las estrategias concretas que definen el comportamiento.
El contexto es quien ejecuta la estrategia o comportamiento.
Vamos a definir cuál sería la estrategia (interfaz) en el caso del filtrado de transacciones
Vamos a declarar una estrategia para determinar si una transacción debe o no pasar un filtro.
¿Por qué no he llamado a la interfaz TransactionStrategy? Podría ser, pero para este curso quiero que os quedéis con este nombre.
Un predicado es una función matemática/lógica cuyo resultado es sí o no.
Ya definidas las estrategias concretas, lo que debemos definir es el contexto, o sea, donde se ejecuta la estrategia.
Vamos a definir un método que filtre transacciones en función de si el predicado es cierto o no para estas.
Finalmente, tendremos los dos métodos anteriores de la siguiente forma.
La solución es mucho más flexible pero aún así, implica la creación de una clase por tipo de filtro aplicado.
Si el día de mañana el cliente nos pide filtrar por otro concepto, crearíamos una estrategia nueva y ya está.
Si quisiéramos, podríamos incluso saltarnos la creación de las dos clases con las estrategias concretas y definirlas en el mismo método mediante clases internas.
De esta forma nos ahorraríamos la creación de dos ficheros distintos, con todo lo que conlleva (imports, package, tests unitarios).
Dicho esto vosotros diréis, ¿Y donde está Java 8 en todo esto?
¿Cómo haríamos esto con Java 8?
Vamos a definir el comportamiento de las estrategias concretas en dos métodos, en vez de dos clases.
Y a continuación es cuando los que llevamos un tiempo trabajando con Java y vemos esto…
… cuando empezamos a vomitar arcoíris.
Como veis, el método filterTransactions no lo hemos modificado. Sigue aceptando una lista de transacciones y un objeto de tipo TransactionPredicate. Pero en vez de una instancia, le hemos pasado una referencia a un método (parametrizado un comportamiento).
Pero vamos más allá.
¿Qué pasaría si no nos interesara definir los métodos isBig y isSmall porque no son relevantes en otro contexto que el del filtrado de transacciones?
Pasaría que los que llevamos un tiempo trabajando con Java, al ver esto…
… además de vomitar arcoíris, vomitaríamos estrellitas.
Esto que vemos aquí es una expresión lambda que define, de la misma forma que los métodos del ejemplo anterior, y que las estrategias concretas, un comportamiento.
Este comportamiento sigue siendo el mismo.
Hemos definido comportamientos de 3 formas distintas. Con objetos, con referencias a métodos y con expresiones lambdas. Como veis, Java 8 nos ha proveído de 2 maneras más.
Probablemente muchos de vosotros estéis un poco perdidos en relación a lo que habéis visto. Tranquilos, en el próximo tema explicaremos un poco mejor qué es lo que ha sucedido.
Le he llamado así porque las expresiones lambdas (y las referencias a métodos), no son otra cosa más que una definición de una función anónima (como una clase anónima interna).
Al echar la mirada atrás, nos encontramos con que tanto la siguiente expresión lambda, y la referencia al método isBig, ambas se han utilizado como parámetros del método filterTransaction, en concreto el parámetro TransactionPredicate.
¿Qué relación puede haber?
Para entender la relación de estos tres conceptos, detenemos que explicar uno nuevo (para Java).
Una interfaz funcional no es más que una interfaz con un solo método abstracto.
Unos ejemplos bastante conocidos de interfaces funcionales.
Y hay otra interfaz que os sonará un poco.
Creo que igual ya sabéis por donde voy.
Una interfaz funcional no es más que una interfaz con un solo método abstracto.
Unos ejemplos bastante conocidos de interfaces funcionales.
Y hay otra interfaz que os sonará un poco.
Creo que igual ya sabéis por donde voy.
Dicho esto. ¿Qué es una expresión lambda?
Es una forma concisa de definir una implementación anónima de una interfaz funcional.
Ahora veremos cada parte por separado
Bloque: A diferencia del método, en el que el tipo del dato de salida está en la especificación del mismo, aquí es el bloque el que lo especifica y debe ser consistente.
Ya sabiendo todo esto, podemos decir…
Que como una expresión lambda no es más que una forma de especificar una instancia de una clase anónima que implementa una interfaz funcional, como es Comparator, estos dos trozos de código hacen lo mismo.
Volviendo al ejemplo del principio.
¿Por qué ha compilado el código? ¿Qué relación hay entre la expresión lambda y la interfaz?
Una expresión lambda se puede usar como una instancia de una interfaz funcional cuando sus parámetros y su tipo de retorno coinciden con el de su único método abstracto de esa interfaz funcional.
En ambos casos hay un parámetro de entrada, cuyo tipo es inferido en la expresión lambda por el contexto.
A su vez vemos que el tipo devuelto por ambos, expresión y método, es booleano.
Esto hace que la expresión lambda pueda ser usada como TransactionPredicate.
Un ejemplo un poco más complejo.
Tenemos esta interfaz funcional Function que está definida con generics. ¿Qué significa? Que esta misma interfaz nos servirá para definir una infinidad de tipos de funciones de un parámetro de entrada y que devuelvan un tipo distinto a void.
Por ejemplo.
¿Y la anotación @FunctionalInterface?
Le indica al compilador que es una interfaz funcional, y por lo tanto no puede tener más de un método.
Por último, Java provee una serie de interfaces funcionales, como Function, que nos servirán en la mayoría de casos. A continuación le echaremos un vistazo a las más relevantes.
Representa un predicado funcional, lo que es lo mismo, una función que para un parámetro de entrada devuelve verdadero o falso.
Este sería un ejemplo para una cadena de caracteres.
Y vosotros diréis “Esto me suena de algo”. Hemos creado TransactionPredicate que es básicamente lo mismo.
El siguiente es un Consumer, que como dice su nombre, consume. ¿Qué quiere decir? Que acepta/consume un parámetro de entrada pero no deuelve nada.
La siguiente interfaz es Supplier/Proveedor. Su misión es proveer de objetos.
La última interfaz es Runnable. No es parte del paquete java.útil.function pero en Java8 ha obtenido la anotación FunctionalInterface.
Si tuviéramos un método privado llamado sendEmail(), podríamos crear una instancia de Runnable de la siguiente manera.
Ya que tenemos el método privado, también podríamos crear un Runnable así.
Esta es una referencia a un método. En concreto, al método sendEmail, ejecutado en el contexto de esta instancia (this)
Al usar referencias directas a métodos, además de ganar legibilidad, reducimos la “verbosidad”.
Se compone por el destino, un doble doble punto, y el nombre del método.
El target o blanco o destino define el contexto en el que se ejecutará el método, o dicho de otra forma, quién ejecutará el método.
El target o blanco o destino define el contexto en el que se ejecutará el método, o dicho de otra forma, quién ejecutará el método.
Como vemos, hay dos formas de referenciar al mismo método. ¿Cuál es la diferencia?
A continuación, y ya para terminar la clase, haremos una comparación entre referencias a métodos y sus equivalentes expresiones lambdas para tener los conceptos más claros.