Java 8: Lambdas e Interfaces Funcionales

Java 8: Lambdas e Interfaces Funcionales

En Java 8 se agregó la capacidad de escribir código usando Programación Funcional, que es una forma de escribir código de una manera más declarativa. Usted especifica lo que quiere en lugar de tratar con el estado de los objetos. Te centras más en las expresiones que en los loops.

La programación funcional utiliza expresiones lambda para escribir código. Puedes pensar en la expresión lambda como un método sin nombre. Tiene parámetros y un cuerpo como los métodos. Las expresiones lambda se denominan comúnmente lambdas para abreviar.


Ejemplo de Lambda

Nuestro objetivo es imprimir todos los animales en una lista de acuerdo con algunos criterios. Mostraremos cómo hacer esto sin lambdas para ilustrar cuán útiles son. Empezaremos con la clase Animal:


La clase Animal tiene tres variables de instancia que se insertan en el constructor. Además, hay dos métodos que recuperan el estado del objeto si puede saltar (hop) o nadar (swim), también tiene un método toString(), de esta manera podemos identificar fácilmente el Animal en los programas.

Necesitamos una interfaz. Por ahora, debemos recordar que una interfaz especifica los métodos que nuestra clase necesita implementar:

Lo primero que debemos comprobar es si el animal puede saltar (can hop). Proporcionamos una clase que puede comprobar esto:

Esta clase puede parecer sencilla y en realidad lo es. Esto es parte del problema que resuelven las lambdas. Ahora tenemos todo lo que necesitamos para escribir nuestro código para encontrar los animales saltarines (can hop):


El método print() es muy específico: puede comprobar cualquier rasgo. No debería saber qué estamos buscando específicamente para imprimir la lista de animales. Ahora bien, ¿qué pasaría si quisiéramos imprimir animales que nadan? Tendremos que escribir otra clase, CheckIfSwims. Entonces necesitamos agregar una nueva línea que instancia la clase.

¿Por qué no especificamos aquí la lógica que nos interesa? Podríamos iterar toda la clase y hacerte buscar la única línea que cambió. En cambio, te lo mostraremos. Podríamos sustituir dicha línea por el siguiente código:

print(animals, a -> a.canHop());

Le estamos diciendo a Java que sólo nos importan los animales que saltan (hop). No hace falta mucha imaginación para descubrir cómo podríamos agregar lógica para recuperar animales nadadores (swim). Tenemos que agregar solo una línea de código; no es necesaria una clase adicional para hacer algo simple:

print(animals, a -> a.canSwim());

¿Qué pasa con los animales que no nadan?

print(animals, a -> !a.canSwim());

Sintaxis Lambda

Una de las expresiones lambda más simples que puedes escribir es la siguiente:


a -> !a.canHop()

La sintaxis lambda es complicada porque muchas partes son opcionales. Estas dos líneas hacen exactamente lo mismo:


Veamos qué sucede aquí, el primer ejemplo tiene tres partes:

  • Un único parámetro que se especifica con el nombre a;
  • El operador de flecha para separar el parámetro y el cuerpo;
  • Un cuerpo que llama a un único método y devuelve el resultado de ese método.

El segundo ejemplo muestra la forma más detallada de lambda que devuelve un valor booleano:

  • Un solo parámetro que especifica el nombre a, con el tipo Animal;
  • El operador de flecha para separar el parámetro del cuerpo;
  • Un cuerpo que tiene una o más líneas de código, incluido un punto y coma y una declaración de devolución.

Introduciendo Functional Interfaces

Lambdas trabaja con interfaces que tienen un único método abstracto, llamado interfaces funcionales, este método obedece a una regla llamada Single Abstract Method (SAM).

Predicate

Puedes imaginar que podríamos crear varias interfaces como esta usando lambdas. queremos probar Animals, Strings, Plants y cualquier otro elemento que encontremos. Afortunadamente, Java reconoce que este es un problema común y nos proporciona dicha interfaz. Esto está en el paquete java.util.function y su esencia es la siguiente:


Esta interfaz se parece mucho a nuestro método test(Animal). La única diferencia es que usa el tipo T en vez de Animal. Esta es la sintaxis de generics. Es como si creáramos un ArrayList y tuviéramos que especificar cualquier tipo que vaya en él. Esto significa que ya no necesitamos nuestra propia interfaz y podemos poner cualquier elemento relacionado con nuestra búsqueda en una clase:

En este código, esperamos tener un Predicate que usa el tipo Animal. Podemos usarlo sin necesidad de escribir código extra.

Consumer

La interfaz funcional Consumer posee un método que necesitas saber:

void accept(T t)

¿Por qué querrías recibir un valor y no devolverlo? Una razón común es al imprimir un mensaje:

Consumer<String> consumer = x -> System.out.println(x);

Declaramos una funcionalidad para imprimir el valor que no se dio. Vale, aún no tenemos un valor. Cuando el Consumer es llamado, el valor será proporcionado y luego impreso. Echemos un vistazo a un código que utiliza un Consumer:


Este código imprime “Hello World”. El método print() acepta un Consumer que sabe imprimir un valor. Cuando se llama al método accept(), la lambda realmente se ejecuta e imprime el valor.

Supplier

La interfaz funcional Supplier posee solamente un método:

T get()

Un buen caso de uso para un Supplier es al generar valores. Aquí tenemos dos ejemplos:


El primer ejemplo devuelve 42 cada vez que se llama a la lambda. El segundo genera un número aleatorio cada vez que se llama. Podría ser el mismo número, pero lo más probable es que sea un número diferente. Echemos un vistazo a algunos códigos que usan Supplier:


Cuando el método returnNumber() es llamado, invoca el lambda para recuperar el valor deseado. En este caso, el método retorna 42.

Comparator

Esta interfaz es funcional ya que solo tiene un método no implementado. Tiene varios métodos static y default para facilitar la escritura de comparadores complejos. La interfaz Comparator existía antes de que se agregaran lambdas a Java. Como resultado, ella es un paquete diferente. Puedes encontrar el Comparator en java.util.

Sólo necesitas saber sobre el método compare(). ¿Puedes averiguar si ordena en orden ascendente o descendente?

Comparator<Integer> ints = (i1, i2) -> i1 - i2;

El Comparator ints utiliza el orden natural. Si el primer número es mayor, devolverá un número positivo. Supongamos que estamos comparando 5 y 3. El comparador resta 5-3 y devuelve 2. Este es un número positivo, lo que significa que el primer número es mayor y estamos ordenando en orden ascendente.

Analicemos otro caso. ¿Sabías que estas dos declaraciones se pueden ordenar en orden ascendente o descendente?


Ambos Comparators, de hecho, hacen lo mismo: ordenan en orden descendente. En el primer ejemplo, la llamada a compareTo() es "al revés", lo que la hace descendente. En el segundo ejemplo, la llamada utiliza el orden predeterminado; sin embargo, el Comparator aplica un signo negativo al resultado, lo que lo invierte.

Asegúrate de comprender la siguiente tabla para identificar qué tipo de lambda puedes utilizar (en portugués).

Espero que esta guía haya sido de utilidad.

¡Hasta luego!

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.