sábado, 16 de abril de 2016

Expresiones Lambda e Interface Funcionales en Java 8

Una expresión lambda es un bloque de código con una lista de parámetros y un cuerpo separados mediante el operador lambda -> y que representa una instancia a un interface funcional (que explicaré a continuación). Los objetos son la base de Java y no podemos tener una función sin un objeto, por lo que Java solo proporciona soporte para el uso de estas expresiones con los interfaces funcionales.


Antes de empezar y como toma de contacto veamos algunos ejemplos para ver cómo es la sintaxis de una expresión Lambda:
Recibe dos parámetros y devuelve la suma:
(int x, int y) -> { return x+ y ;};

Es posible omitir el tipo de parámetros que recibe, ya que el compilador infiere el tipo, (cuando no ponemos el tipo de parámetro tiene que ser en todos o en ninguno, sino dará un error.)
( x, y) -> { return x+ y ;};

Podemos abreviar aun más nuestra expresión lambda eliminando la palabra clave return, ya que únicamente devolvemos un valor, en este caso debemos eliminar las llaves, ya que no encierran un bloque de código con declaraciones, asignaciones, etc y opcionalmente podemos sustituirlas por paréntesis:
(x,y)->(x+y);

(x,y)->x+y;

A modo de ejemplo de sintaxis podríamos realizar una expresión que no recibe parámetros y devuelve void (aunque no sirva para nada):
( ) -> { }

Y tampoco tiene por qué devolver nada, en el siguiente ejemplo recibe un String y lo muestra por la salida:
msg -> System.out.println(msg); 


Ahora veamos un pequeño ejemplo más funcional comparando una expresión lambda con una clase anónima:

package net.codigoalonso;

public class TutorialLambda {
    
    public static void main(String[] args) {
        
        //Utilizando expresiones Lamda
        IContadorCaracteres contador = (String str) -> str.length();
        
        //Utilizando una clase anónima
        IContadorCaracteres contador2 = new IContadorCaracteres() {
            @Override
            public int contar(String str) {
                return str.length();
            }
        };
        
        //Uso
        String name = "CodigoAlonso.Net";
        int numeroCaracteres = contador.contar(name);        
        System.out.println("Número de Caracteres:" + numeroCaracteres);
        
    }    
}

@FunctionalInterface
interface IContadorCaracteres {
    int contar(String str);            
}

Como puedes observar nos hemos ahorrado bastantes líneas de código y es bastante legible.

¿Qué es un Interface Funcional? 

Es simplemente un interface que tiene exactamente un método abstracto. No es obligatorio utilizar la anotación @FunctionalInterface  pero si bastante recomendable y una buena práctica para evitar añadir métodos extra accidentalmente. Java 8 ha incluido el paquete java.util.function los más utilizados, de esta forma nos ahorramos el declararlos para los usos más comunes.
La lista de los Interface Funcionales predefinidos podemos encontrar en la siguiente url:
https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

Veamos un ejemplo del interface predefinido Function<T,R>

El parámetro T hace referencia al tipo de entrada, R el Tipo de salida:

Por ejemplo para calcular el cuadrado de un número:

Function<Integer,Integer> cuadrado = x -> x * x;
System.out.println("El cuadrado de 5 es: " + cuadrado.apply(5));

Si miramos atrás en nuestro contador de caracteres podríamos hacer lo mismo así:

Function<String,Integer> contadorCaracteres = str -> str.length() ;
String texto = "codigoalonso.net";
System.out.println("Número de caracteres es : " + contadorCaracteres.apply(texto));


Existen variantes para Function<T,R> como por ejemplo IntFunction<R>:
IntFunction<Integer> cuadrado = x -> x * x;
System.out.println("El cuadrado de 5 es: " + cuadrado.apply(5));

Si te has fijado en la descripción habrás observado los métodos andThen() y Compose(), que se utilizan para realizar composiciones.

Veamos un ejemplo para entender su uso, vamos a crear la composición de dos funciones, primero realizaremos el cuadrado de un número y después (andThen) sumaremos uno:

Function<Integer,Integer> cuadrado = x-> x * x;
Function<Integer,Integer> sumar1 = x-> x +1;

Function<Integer,Integer> cuadradoMasUno = cuadrado.andThen(sumar1);       

System.out.println("Cuadrado + 1 es:" + cuadradoMasUno.apply(5));

// Salida: Cuadrado +1 es: 26


En el caso de compose() primero realiza la operación que pasamos como argumento a compose() :

Function<Integer,Integer> numeroMasUnoAlCuadrado = cuadrado.compose(sumar1);       
System.out.println("Numero + 1 al cuadrado:" + numeroMasUnoAlCuadrado.apply(5));
//Salida: Numero + 1 al cuadrado: 36


Otro ejemplo de interface funcionales predefinidos puede ser Predicate<T> que representa una condición que puede ser verdadera o falsa:

Predicate<Integer> divisiblePor3 = x -> x % 3 == 0;

System.out.println("¿ Es 9 divisible por 3? :" + divisiblePor3.test(9));



No hay comentarios:

Publicar un comentario