java-lambdas-clases-anonimas-sintaxis

Clases anónimas y expresiones lambda en Java

  • 4 min

Una expresión lambda es una función anónima que implementa el único método abstracto de una interfaz funcional.

Durante casi 20 años, Java tuvo fama de ser un lenguaje verboso. Había que escribir mucho código para hacer algunas tareas sencillas.

Si querías pasar una función a otro método (por ejemplo, “haz esto cuando el usuario haga clic”), tenías que crear una clase entera para envolver esa función.

En 2014 llegó Java 8, que incorporó expresiones lambda y amplió las herramientas de estilo funcional del lenguaje.

El problema: las clases anónimas

Imagina que queremos crear un hilo (Thread) que imprima algo. Thread necesita un objeto Runnable.

A la manera antigua (Java < 8): Como Runnable es una interfaz, no podemos instanciarla directamente. Tenemos que crear una “Clase Anónima” (una clase sin nombre que se define al vuelo).

// MUCHO RUIDO
Thread hilo = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Soy un hilo a la antigua");
    }
});
hilo.start();
Copied!

Fíjate bien. De esas 6 líneas, solo 1 importa (System.out...). Todo lo demás (new Runnable, public void run, llaves, paréntesis) es boilerplate (código basura) que solo sirve para satisfacer al compilador.

La solución: expresiones lambda (->)

Una expresión Lambda es, simplificando mucho, una forma de tratar el código como si fuera un dato. En lugar de pasar un objeto que tiene un método, pasamos el comportamiento directamente.

A la manera moderna (Java 8+):

// CERO RUIDO
Thread hilo = new Thread(() -> System.out.println("Soy un hilo moderno"));
hilo.start();
Copied!

¿Ves la diferencia? Hemos pasado de 5 líneas de “ceremonia” a 1 línea de lógica pura.

La sintaxis de la flecha

Una Lambda tiene tres partes: (Parámetros) -> { Cuerpo }

  1. Parámetros: Lo que recibe la función (a la izquierda).
  2. Operador Flecha (->): Separa los parámetros de la acción.
  3. Cuerpo: Lo que hace la función (a la derecha).

Simplificación de una expresión lambda

Lo mejor de las Lambdas es que Java es muy listo deduciendo tipos (Inferencia). Vamos a ver cómo adelgaza el código paso a paso usando un Comparator de Strings.

Paso 1: Lambda Completa (Explícita)

// Especificamos tipos y usamos llaves y return
(String s1, String s2) -> {
    return s1.length() - s2.length();
}
Copied!

Paso 2: Quitar Tipos (Inferencia) Java sabe que estamos comparando Strings, no hace falta decirlo.

(s1, s2) -> {
    return s1.length() - s2.length();
}
Copied!

Paso 3: Quitar Llaves y Return (Expresión simple) Si el cuerpo es una sola línea, podemos borrar las llaves {} y la palabra return. Se asume implícitamente.

(s1, s2) -> s1.length() - s2.length()
Copied!

¡Boom! De una clase anónima gigante hemos pasado a una sola línea súper legible.

Regla para un solo parámetro: Si la lambda recibe UN solo parámetro, también puedes quitar los paréntesis de la izquierda. nombre -> System.out.println("Hola " + nombre)

¿Dónde puedo usar lambdas?

Aquí está el truco. No puedes usar una Lambda donde te dé la gana. Solo puedes usar una Lambda donde Java espere una Interfaz Funcional.

¿Y qué es una Interfaz Funcional? Es una interfaz que tiene EXACTAMENTE UN método abstracto.

  • Runnable: Solo tiene run(). -> vale.
  • Comparator: Solo tiene compare(). -> vale.
  • ActionListener: Solo tiene actionPerformed(). -> vale.

Si la interfaz tuviera 2 métodos abstractos, Java no sabría a cuál de los dos corresponde tu Lambda, y daría error.

Muchas interfaces antiguas (Runnable, Callable) se volvieron funcionales retroactivamente. Además, Java creó un paquete entero java.util.function lleno de interfaces nuevas listas para usar, que veremos en el próximo artículo.

Variables externas

Las Lambdas pueden usar variables que están fuera de su cuerpo (esto se llama Closure o Clausura), pero con una condición estricta: La variable no puede cambiar.

int contador = 10;

Runnable r = () -> {
    // Podemos LEER contador
    System.out.println("Valor: " + contador);

    // contador++; // ¡ERROR DE COMPILACIÓN!
};
Copied!

Java exige que la variable sea final o “effectively final” (que no se modifique nunca después de inicializarse). Si necesitas modificar variables externas, tendrás que usar envoltorios atómicos o arrays de 1 posición (hacks que veremos más adelante).