- :)
- :0
- :D
- ;)
- :]
- :º
Aprende sobre Programación Funcional en Java, o también llamada Lambdas, una gran técnica para escribir código mantenible y eficiente
Artículo Técnico
La programación funcional o también llamadas las expresiones lambda han supuesto un salto importante en la evolución del lenguaje de programación Java. Te mostramos una introducción de cómo se usan y cómo escribir más con menos.
Qué son las funciones lambdas de Java
Las funciones lambdas es un término adoptado de la programación funcional y corresponden con funciones de Java que normalmente son anónimas y se escriben en línea allí donde se usan. Como cualquier función recibe cero o más argumentos y devuelven uno o ningún valor de retorno. Como cualquier función, puede consumir métodos de otras clases y objetos. Al declararse al mismo tiempo en donde se usa, puede acceder a las variables locales del ámbito al que pertenece, pero sólo podrá usar estos como valores de sólo lectura, impidiendo realizar alguna modificación.
Las funciones lambdas se crearon a partir de la versión Java 8, por lo que no es posible usar su sintaxis en versiones anteriores. Sus entidades principales están contenidas en el package java.util.funcional. Sí hay que decir, que no aportan una funcionalidad que no pueda hacerse con Java pre 8, simplemente es una manera más compacta de escribir código Java. Se puede decir de manera resumida que una función lambda es como una clase con un único método público. Así que los que no dispongan de Java 8 podrían simular un comportamiento similar creando clases parecidas a las proporcionadas con el API de Java en el package java.util.functional.
Entidades de java.util.functional
Antes de crear una función de tipo lambda, conviene conocer las entidades básicas que componen esta manera de programar. Las principales entidades son interfaces con un único método que debe implementar el programador y que estas implementaciones pueden hacerse llegar como argumentos de métodos de otras muchas clases del API de Java. Hubo una gran modificación de las clases existentes para aceptar este tipo de implementaciones allí donde tuviera sentido, como ocurre en las colecciones.
Las implementaciones de estas interfaces son del tipo, consume un valor y retorna otro tipo de valor, o produce un valor sin argumentos o produce un valor dados dos argumentos. A éstas se les llama unidades funcionales porque componen una lógica interna que a priori el consumidor de esta lógica no conoce, pero de la que sí se conoce su interfaz y por tanto la manera de relacionarse con el resto de los objetos, o lo que es lo mismo la manera de ser invocada. Aparece de nuevo el concepto de cajas negras en donde entran parámetros y salen resultados.
Las interfaces funcionales más importantes contenidas en java.util.functional son:
- Supplier<T>: esta función se debe utilizar cuando se necesiten generar objetos sin requerir argumentos. Por ejemplo para realizar una inicialización perezosa.
- Consumer<T> esta en cambio es el opuesto de
Supplier
ya que consume, acepta como argumento el tipoT
sin devolver ningún valor de retorno. - Function<T,R> esta interfaz permite definir una función que acepta un parámetro de tipo
T
y devuelve un resultado del tipoR
pudiendo aplicarle alguna transformación u operación. - BiFunction<T,R,S> esta interfaz permite definir una función que acepta dos parámetros de
tipo
T
yR
, devolviendo un resultado del tipoS
. Normalmente serán operaciones de agregación u operadores binarios como la suma, resta, etc.. - Predicate<T> la interfaz predicado debe devolver forzosamente un
boolean
dado un objeto de tipoT
, normalmente utilizado para filtrar elementos de una colección.
El paquete incluye más interfaces que el programador puede usar, pero estas son las más básicas con las que ya es posible empezar a realizar algunos ejemplos útiles y frecuentes.
Cómo se crea una función lambda
La sintaxis cambia un poco respecto a Java tradicional, ya que se intentan no escribir los tipos de las variables siempre y cuando no se cree alguna ambigüedad. Veamos el primer ejemplo:
Function<String,Integer> sizeOf = (String s) -> {
return s.length();
};
O su equivalente y más compacta:
Function<String,Integer> sizeOf = s -> s.length();
En ambos casos se está definiendo una función que dado un String
devolverá la longitud de la
cadena de caracteres que almacene. Fíjese que el tipo de la variable de s
se infiere
automáticamente de los tipos utilizados en sizeOf
y que la palabra reservada return
no es
necesaria, siempre y cuando no haya más de una sentencia en la función.
Por rara y compacta que pueda parecer la sintaxis, no es más que otra forma de escribir la siguiente clase, de hecho esto es lo que realmente genera el compilador:
public class SizeOf implements Function<String,Integer>{
public Integer apply(String s){
return s.length();
}
}
Así que para poder usar tanto la función sizeOf
como la clase SizeOf
en un bloque de código
cualquiera, se realizaría de la siguiente forma:
Integer r1 = sizeOf.apply("hola java 8");
// o
Integer r2 = new SizeOf().apply("hola java 8");
La ventaja de hacerlo como función, en lugar de como clase, a parte de la reducción de los literales que acompañan a cada opción, es que el API del JDK de Java versión 8 y adelante tiene métodos que aceptan estas funciones, reduciendo aun más la cantidad de código que se debe escribir.
Por ejemplo veamos como podemos aplicar una ordenación usando un comparador escrito en Java 8:
class Persona{
String nombre;
Persona (String nombre){
this.nombre = nombre;
}
}
List<Persona> personas = new ArrayList<>();
personas.add(new Persona("Pepe"));
personas.add(new Persona("Andrés"));
personas.sort( (l, r) -> l.nombre.compareTo(r.nombre));
Esta última función lambda es una del tipo BiFunction
que acepta dos objetos de tipo Persona
y
devuelve un int
típico de cualquier comparador de java java.util.Comparator<T>
. Tanto la función
como el comparador son compatibles, así que la función lambda anónima, se podría haber guardado en una variable de tipo comparador para ser usado otra vez más adelante:
Comparator<Persona> comp = (l, r) -> l.nombre.compareTo(r.nombre));
personas.sort(comp);
El uso de lambda en colecciones
El uso más habitual de lambda es en las colecciones y se utiliza para definir una operación que será
aplicada a los elementos contenidos en ésta, dejando que el Api de Java realice la iteración sobre
los elementos por nosotros, sin que tengamos que escribir ninguna sentencia de control de iteración
como for
o while
.
Para poder aplicar funciones lambda en colecciones, en Java 8, se introduce una nueva entidad
denominada Stream
. Los Stream
representan flujos de datos que pueden ser infinitos, pero en
general el flujo estará asociado a una colección finita como un ArrayList
. Así, en un Stream
el
programador puede «registrar» operaciones que se harán sobre una lista, por ejemplo. Para ello las
colecciones incorporar un método .stream()
con el cual acceder al correspondiente flujo.
Stream<Persona> stream = personas.stream();
List<String> nombres = stream
// filtrado de los elementos que tienen nombre nullo
.filter(p -> p.nombre!=null)
// aplicar una conversión, de Persona a String
// quedándonos con el nombre
.map(p -> p.nombre)
// a partir de aquí se tiene un Stream<String>
// recolectar los elementos en una lista
.collect(Collectors.toList());
Conclusiones
En el código anterior, se definen varias operaciones (filtrado, conversión y recolección) que dan
como resultado la obtención de todos los nombres de una colección de Personas en una instancia del tipo List<String>
sin necesidad de escribir un bucle.
Este tipo de sintaxis es muy compacta y se utiliza intensivamente en grandes proyectos para reducir el número de líneas necesarias. Además obliga al programador a escribir unidades funcionales que son fácilmente testeables desde pruebas unitarias con jUnit.
Desde luego hay muchas más opciones y funciones lambda que pueden usarse, esto es sólo algún ejemplo de cómo utilizar las más comunes. Si estás interesado en aprender más de Java y la sintaxis introducida en Java 8 te recomendamos que revises nuestro libro de java Guía Javañol.
ÍNDICE
RELACIONADOS
CATEGORÍAS
java
tutorial
Mantente Conectado
Newsletter
¡Mantente al día con lo último en tecnología y negocios! Suscríbete a nuestra newsletter y recibe actualizaciones exclusivas directamente en tu correo.
Reunión Online
No dejes pasar la oportunidad de explorar nuevas posibilidades. ¡Agenda una reunión online con nosotros hoy y comencemos a construir juntos el futuro de tu negocio!
- :D
- :)
- ;]
- :0
- :}
- ;)
- :>
Únete al Equipo
Contamos con una gran cartera de noveles que compaginan su formación académica con la experiencia en Arteco, aprendiendo de la mano de los que están en primera línea. Realizamos un programa intensivo de formación cara a la rápida incorporación en equipos de desarrollo reales.
- :)
- :0
- :D
- ;)
- :]
- :º