HOWTO · Java
El operador de flecha -> en Java
Descubre qué significa la sintaxis `->` de Java y cómo usar lambdas con interfaces funcionales, colecciones, flujos e hilos.
En esta página
En Java, -> separa los parámetros del cuerpo de una expresión lambda; no es un operador de propósito general para realizar cálculos. Una lambda proporciona una implementación para una interfaz funcional compatible, es decir, una interfaz con un único método abstracto. Las lambdas se incorporaron en Java 8 y pueden acortar algunas implementaciones, aunque no sustituyen a todas las clases anónimas ni garantizan un código más rápido o claro.
Qué significa el operador de flecha en Java
Antes de Java 8, una tarea para Runnable podía escribirse como una clase anónima con un método run. Una lambda expresa el mismo comportamiento así: () -> System.out.println("Working"). La flecha forma parte de una expresión lambda; no crea un valor de función independiente de un tipo de destino. El compilador determina la interfaz funcional a partir del contexto, como una declaración de variable o un argumento de método.
Sintaxis y reglas de las expresiones lambda
Una lambda tiene una lista de parámetros, una flecha y un cuerpo. El cuerpo puede ser una expresión sin llaves, como x -> x * x; si es un bloque, se escribe entre llaves y requiere un return explícito cuando el método de destino devuelve un valor. Una lambda sin parámetros usa (), y una con varios parámetros los escribe entre paréntesis, como en (left, right) -> left + right. También se permiten paréntesis alrededor de un único parámetro, por ejemplo (name) -> name.length().
En una lambda con cuerpo de bloque que devuelve un valor, el tipo de retorno del método de destino determina el tipo del resultado. Este ejemplo asigna la lambda a ToIntFunction<String>, cuyo método applyAsInt devuelve un int:
El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.
import java.util.function.ToIntFunction;
public class BlockLambdaExample {
public static void main(String[] args) {
ToIntFunction<String> length = (String name) -> {
return name.length();
};
System.out.println(length.applyAsInt("Mira"));
}
}
El cuerpo debe respetar el tipo de retorno del método de destino y sus reglas sobre excepciones comprobadas. Una lambda no se puede asignar a una clase arbitraria ni a una interfaz con varios métodos abstractos no relacionados. Los métodos default y static de una interfaz no cuentan como métodos abstractos adicionales para esta regla.
Uso de lambdas con interfaces funcionales
Una lambda se puede asignar a una interfaz funcional pequeña e invocar mediante el método de esa interfaz. Este ejemplo autónomo coloca primero la clase que contiene main, de modo que se puede ejecutar directamente. Su salida es la línea que imprime draw().
Ejecuta este ejemplo:
El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.
public class ArrowExample {
@FunctionalInterface
interface Drawable {
void draw();
}
public static void main(String[] args) {
Drawable shape = () -> System.out.println("Drawing a circle");
shape.draw();
}
}
Drawing a circle
Usos habituales en colecciones, flujos e hilos de Java
Los métodos de colecciones, como forEach, aceptan una interfaz funcional, por lo que una lambda puede indicar qué hacer con cada elemento. En este ejemplo, name representa el elemento actual de la lista y la lambda de Consumer<String> lo imprime:
El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.
import java.util.Arrays;
import java.util.List;
public class CollectionLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira");
names.forEach(name -> System.out.println(name));
}
}
El método filter de un flujo recibe una lambda Predicate<String> para conservar los elementos que cumplen la condición. La operación forEach siguiente imprime cada nombre que supera el filtro:
import java.util.Arrays;
import java.util.List;
public class StreamLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira", "Bo");
names.stream()
.filter(name -> name.length() > 2)
.forEach(name -> System.out.println(name));
}
}
Estas API ofrecen distintas formas de expresar la iteración y el procesamiento, pero un bucle tradicional puede ser más adecuado si facilita seguir el flujo de control.
Runnable suele usarse para la tarea de un hilo porque su único método abstracto no recibe argumentos ni devuelve un valor. Esta lambda proporciona la tarea Runnable; construir el Thread no la ejecuta, mientras que start() programa su ejecución independiente:
public class ThreadLambdaExample {
public static void main(String[] args) {
Runnable task = () -> System.out.println("Task running");
Thread worker = new Thread(task);
worker.start();
}
}
La salida del hilo puede aparecer en un orden distinto al de otras tareas concurrentes, y la planificación no está garantizada. Usa lambdas cuando la interfaz de destino y el código circundante permitan entender claramente el comportamiento; reducen la sintaxis, pero no eliminan la necesidad de comprender la API ni el comportamiento concurrente.