1. Introducción
Method reference (o, en español, referencia a método) es una sintaxis especial en Java que permite pasar un método ya existente (o un constructor) como implementación de una interfaz funcional. Se puede «pasar» un método allí donde se espera una expresión lambda, si las firmas coinciden.
Sintaxis:
Clase::método
objeto::método
Clase::new
Si una lambda es una mini función «al vuelo», entonces una method reference es simplemente «pasa un método ya existente». Es parecido a, en lugar de copiar una receta, dar el enlace a la página con la receta.
Ejemplo sencillo
En lugar de esto:
list.forEach(s -> System.out.println(s));
Así:
list.forEach(System.out::println);
Se ve conciso, ¿verdad?
2. Tipos de method references
Las referencias a métodos tienen cuatro formas principales. Son estas — y no necesitas nada más.
Referencia a un método estático
Sintaxis: Clase::métodoEstático
Ejemplo:
Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"
Lo mismo con una lambda:
Function<Integer, String> intToString = i -> String.valueOf(i);
Referencia a un método no estático de un objeto
Sintaxis: objeto::método
Ejemplo:
PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("¡Hola, mundo!");
Equivalente a una lambda:
Consumer<String> consumer = s -> printer.println(s);
Referencia a un método no estático de la clase
Sintaxis: Clase::método
Aquí, el primer parámetro de la interfaz funcional se convierte en el objeto sobre el que se invoca el método.
Ejemplo:
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4
Aquí String::length se convierte en la función: (String s) -> s.length()
Referencia a un constructor
Sintaxis: Clase::new
Ejemplo:
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
Equivalente a una lambda:
Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();
3. ¿Cuándo usar method reference?
Es útil usar method reference cuando la lambda solo llama a un método existente sin lógica adicional — así el código se vuelve más corto y legible.
Ejemplo: ordenar una lista
En lugar de esto:
List<String> names = Arrays.asList("Iván", "Pedro", "Ana");
names.sort((a, b) -> a.compareToIgnoreCase(b));
Así:
names.sort(String::compareToIgnoreCase);
Ejemplo: procesar colecciones
En lugar de:
list.forEach(s -> System.out.println(s));
Se puede aún más corto:
list.forEach(System.out::println);
Ejemplo: transformar elementos
En lugar de:
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Se puede:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Más detalles sobre el Stream API y la clase de utilidades Collectors los verás en el nivel 30 :P
4. Comparación entre method reference y expresiones lambda
Equivalencia
Method reference y una expresión lambda suelen ser intercambiables. Ambas implementan una interfaz funcional si las firmas coinciden.
Ejemplo:
Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;
¿Cuándo es mejor usar method reference?
- Cuando la lambda solo llama a un método existente sin lógica adicional.
- Para mejorar la legibilidad, especialmente en cadenas de llamadas largas.
- Cuando quieres dejar claro: «aquí solo hay una llamada al método».
¿Cuándo no conviene method reference?
- Si se necesita lógica adicional (validación, condiciones, gestión de errores).
- Si hay que transformar los parámetros antes de invocar el método.
Ejemplo:
list.forEach(s -> {
if (s != null) System.out.println(s);
});
// Aquí no sirve method reference; solo una lambda.
5. Práctica: reescribimos expresiones lambda usando method references
Ejemplo 1: imprimimos nombres de animales
List<String> animals = Arrays.asList("Gato", "Perro", "Loro");
animals.forEach(animal -> System.out.println(animal));
Se convierte en:
animals.forEach(System.out::println);
Ejemplo 2: convertimos cadenas en números
List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Se convierte en:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Ejemplo 3: ordenar objetos por nombre
List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));
Se convierte en:
animalList.sort(Comparator.comparing(Animal::getName));
Aquí Animal::getName es una referencia a un método no estático de la clase.
Ejemplo 4: crear objetos a través del constructor
Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();
Se convierte en:
Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();
6. Cómo funciona la correspondencia de firmas
Una method reference solo se puede usar cuando la firma del método coincide con el método abstracto de la interfaz funcional.
@FunctionalInterface
interface IntToString {
String convert(int value);
}
public class Demo {
public static String intToHex(int value) {
return Integer.toHexString(value);
}
public static void main(String[] args) {
IntToString converter = Demo::intToHex;
System.out.println(converter.convert(255)); // ff
}
}
Aquí Demo::intToHex encaja porque recibe un int y devuelve un String.
7. Method reference y constructores con parámetros
Si el constructor recibe parámetros, se puede seguir usando una method reference — siempre que las firmas coincidan.
@FunctionalInterface
interface AnimalFactory {
Animal create(String name);
}
class Animal {
private String name;
public Animal(String name) { this.name = name; }
public String getName() { return name; }
}
AnimalFactory factory = Animal::new;
Animal cat = factory.create("Kitty");
System.out.println(cat.getName()); // Kitty
8. Errores típicos al usar method references
Error n.º 1: Incompatibilidad de firmas.
Si la firma del método no coincide con el método abstracto de la interfaz, el compilador dará error. Por ejemplo, la interfaz espera dos parámetros, pero la referencia apunta a un método con un solo parámetro.
Error n.º 2: Intentar usar una referencia a un método no estático de clase sin un objeto.
Cuando se usa la forma Clase::método, el primer parámetro de la interfaz se convierte en el objeto de la llamada. Si confundes la cantidad o el orden de los parámetros, obtendrás un error de correspondencia.
Error n.º 3: Usar una method reference donde se necesita lógica adicional.
Si se requiere una condición, registro (logging) u otra gestión de excepciones, usa una lambda, no una referencia a método.
Error n.º 4: Referencias a métodos sobrecargados.
Si una clase tiene varios métodos con el mismo nombre, el compilador puede no saber cuál elegir. A veces ayuda indicar explícitamente el tipo de la interfaz funcional.
Error n.º 5: Usar una referencia a un método no estático sin un objeto.
Por ejemplo, String::toUpperCase funciona correctamente en map, porque el primer parámetro es el propio objeto String. Pero fuera de un contexto adecuado donde se espera un método estático, esto llevará a un error.
GO TO FULL VERSION