Expresiones lambda
Las expresiones lambda han sido una de las principales novedades incorporadas en la versión Java 8, que pretenden acercar el lenguaje a la programación funcional.
Una expresión lambda es una expresión de código que genera un objeto que implementa una interfaz funcional, es decir, aquella que solamente dispone de un único método abstracto. Por tanto, lo que hacemos a la hora de escribir una expresión lambda es proporcionar la implementación del único método abstracto de la interfaz.
La expresión lambada tiene dos partes:
parametros -> implementación
Toda ella devuelve como hemos dicho un objeto que implementa dicha interfaz, por lo que el resultado de la expresión se suele almacenar en una variable del tipo de la interfaz:
Interfaz variable = parametros->implementación
Reglas
A la hora de construir una expresión lambda hay que seguir una serie de reglas, tanto para los parámetros como para la implementación. Vamos a comentarlas a continuación.
Parametros
Si el método a implementar de la interfaz no tiene parámetros, se indicarán los paréntesis ():
() - > implementación
Si el método tiene un parámetro, se puede omitir en la declaración tanto el tipo como los paréntesis:
a-> implementación
Pero si se opta por indicar el tipo, es obligatorio poner los paréntesis:
(int x)->
En caso de que el método disponga de más de un parámetro, es obligatorio indicar estos entre parentesis, aunque el tipo es opcional:
(a,b)->
(int k, String s)->
Por ejemplo, las siguientes declaraciones de parámetros en una expresión lambda son correctas:
()->
n->
(int p)->
(x)->
(a,b)->
(int x, String v)->
Pero las siguientes no lo son:
-> //si no hay parámetros, se deben poner los paréntesis
int p-> //si se indica el tipo, hay que poner los paréntesis
a,b-> //cuando son más de un parámetro, obligatorio paréntesis
(int s, c)-> //hay que indicar el tipo en los dos parámetros
Implementación
A la hora de definir la implementación de una expresión lambda, vamos a distinguir dos casos, cuando la implementación contiene una única instrucción y cuando consta de más de una .
En caso de que la implementación contenga una única instrucción, podemos omitir las llaves para encerrar el código de dicha implementación:
a->System.out.println(a);
Si es una instrucción que devuelve resultado, no son obligatorias ni las llaves ni la palabra return:
(a, b)->a.equals(b);
Si se quiere indicar la palabra return, entonces sería obligatorio también poner las llaves:
(a,b)->{return a.equals(b);};
En caso de que la implementación conste de más de una instrucción, entonces si que será obligatorio encerrar todo el código entre llaves, tanto si devuelve resultado como si no:
(a, b)->{int s=a+b;System.out.println(s);};
(x)->{x++;return x*2;};
En estos casos de implementaciones con más de una instrucción, donde se debe devolver un resultado, es obligatorio indicar la palabra return en la última instrucción.
Ejemplo
Dada la siguiente interfaz funcional:
interface Prueba{
String apply(int a);
}
Las siguientes serían implementaciones correctas de la interfaz mediante expresiones lambda:
n->"hola"+n;
(int k)->{k++; return String.valueOf(k);};
Las siguientes implementaciones de la interfaz no serían correctas:
n->System.out.println(n); //la implementación debe devolver un String
(int p)->return String.valueOf(p); //con return hay que poner llaves
Referencias a métodos
Cuando la implementación de una expresión lamba se compone de una única instrucción que realiza una llamada a un método, toda la expresión puede ser sustituida por una referencia a método. La sintaxis de la referencia a método es:
objeto::metodo
o
clase::metodo
donde los parámetros de llamada a método son determinados implícitamente por el contexto.
Por ejemplo, dada la expresión lambda:
a->System.out.println(a);
toda ella puede ser sustituida por la siguiente referencia a método:
System.out::println
El parámetro que se pasará a println se determina impícitamente, y será el utilizado en la llamada al método de la interfaz.
Por ejemplo, dada la interfaz:
interface Test{
void hacer(int k);
}
Una manera de crear un objeto que implemente esta interfaz y llamar al método hacer con un determinado número, sería usando expresiones lambda:
Test t=n->System.out.println(n);
t.hacer(10); //imprime el número 10
Lo mismo con referencia a métodos sería:
Test t=System.out::prinln;
t.hacer(10);
Otro ejemplo
Dada la interfaz:
interface Calculo{
String get();
}
Y dado el siguiente objeto númerico:
Integer n=100;
Una implementación de la interfaz que convierte el número a String sería:
Calculo c=()->n.toString();
Con referencia método:
Calculo c=n::toString;