1. Introduzione
Method reference (in italiano — riferimento a metodo) — è una sintassi speciale in Java che consente di passare un metodo (o un costruttore) esistente come implementazione di un’interfaccia funzionale. Puoi “passare” un metodo ovunque sia prevista un’espressione lambda, se le firme coincidono.
Sintassi:
Classe::metodo
oggetto::metodo
Classe::new
Se una lambda è una mini‑funzione “al volo”, allora un method reference è semplicemente “passa un metodo già esistente”. È come, invece di ricopiare una ricetta, fornire il link alla pagina con la ricetta.
Esempio intuitivo
Invece di questo:
list.forEach(s -> System.out.println(s));
Si può così:
list.forEach(System.out::println);
Sembra conciso, vero?
2. Tipi di method reference
I riferimenti ai metodi hanno quattro forme principali. Eccole — e non serve altro.
Riferimento a un metodo statico
Sintassi: Classe::metodoStatico
Esempio:
Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"
Lo stesso con una lambda:
Function<Integer, String> intToString = i -> String.valueOf(i);
Riferimento a un metodo d’istanza di un oggetto
Sintassi: oggetto::metodo
Esempio:
PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("Ciao, mondo!");
Equivalente a una lambda:
Consumer<String> consumer = s -> printer.println(s);
Riferimento a un metodo d’istanza della classe
Sintassi: Classe::metodo
Qui il primo parametro dell’interfaccia funzionale diventa l’oggetto su cui viene invocato il metodo.
Esempio:
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4
Qui String::length si trasforma in una funzione: (String s) -> s.length()
Riferimento al costruttore
Sintassi: Classe::new
Esempio:
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
Equivalente a una lambda:
Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();
3. Quando usare i method reference?
I method reference sono comodi quando la lambda si limita a chiamare un metodo esistente senza logica aggiuntiva — il codice diventa più corto e leggibile.
Esempio: ordinare una lista
Invece di questo:
List<String> names = Arrays.asList("Ivan", "Pietro", "Anna");
names.sort((a, b) -> a.compareToIgnoreCase(b));
Oppure così:
names.sort(String::compareToIgnoreCase);
Esempio: elaborazione di collezioni
Invece di:
list.forEach(s -> System.out.println(s));
Si può anche in modo molto conciso:
list.forEach(System.out::println);
Esempio: trasformazione degli elementi
Invece di:
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Si può fare così:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Maggiori dettagli su Stream API e sulla classe di utilità Collectors li imparerai al livello 30 :P
4. Confronto tra method reference ed espressioni lambda
Equivalenza
I method reference e le espressioni lambda sono spesso intercambiabili. Entrambi implementano un’interfaccia funzionale se le firme coincidono.
Esempio:
Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;
Quando è meglio usare i method reference?
- Quando la lambda chiama semplicemente un metodo esistente senza logica aggiuntiva.
- Per migliorare la leggibilità, soprattutto in catene di chiamate lunghe.
- Quando vuoi rendere esplicito: “qui è solo una chiamata di metodo”.
Quando un method reference non è adatto?
- Se serve logica aggiuntiva (validazione, condizioni, gestione degli errori).
- Se i parametri devono essere trasformati prima della chiamata del metodo.
Esempio:
list.forEach(s -> {
if (s != null) System.out.println(s);
});
// Qui un method reference non va bene, serve una lambda.
5. Pratica: riscrivere le lambda usando i method reference
Esempio 1: stampiamo i nomi degli animali
List<String> animals = Arrays.asList("Gatto", "Cane", "Pappagallo");
animals.forEach(animal -> System.out.println(animal));
Diventa:
animals.forEach(System.out::println);
Esempio 2: convertiamo stringhe in numeri
List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Diventa:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Esempio 3: ordinare oggetti per nome
List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));
Diventa:
animalList.sort(Comparator.comparing(Animal::getName));
Qui Animal::getName è un riferimento a un metodo d’istanza della classe.
Esempio 4: creare oggetti tramite costruttore
Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();
Diventa:
Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();
6. Come funziona la corrispondenza delle firme
Un method reference si può usare solo quando la firma del metodo coincide con il metodo astratto dell’interfaccia funzionale.
@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
}
}
Qui Demo::intToHex va bene perché accetta int e restituisce String.
7. Method reference e costruttori con parametri
Se il costruttore accetta parametri, si può comunque usare un method reference — purché le firme coincidano.
@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("Barsik");
System.out.println(cat.getName()); // Barsik
8. Errori tipici nell’uso dei method reference
Errore n. 1: mancata corrispondenza delle firme.
Se la firma del metodo non corrisponde al metodo astratto dell’interfaccia, il compilatore segnalerà un errore. Per esempio, l’interfaccia si aspetta due parametri, mentre il riferimento punta a un metodo con un solo parametro.
Errore n. 2: tentare di usare un riferimento a un metodo d’istanza della classe senza oggetto.
Quando usi la forma Classe::metodo, il primo parametro dell’interfaccia diventa l’oggetto della chiamata. Se confondi numero o ordine dei parametri — otterrai un errore di corrispondenza.
Errore n. 3: usare un method reference dove serve logica aggiuntiva.
Se servono condizioni, logging o gestione delle eccezioni — usa una lambda, non un riferimento a metodo.
Errore n. 4: riferimenti a metodi sovraccarichi.
Se una classe ha più metodi con lo stesso nome, il compilatore potrebbe non capire quale scegliere. A volte aiuta indicare esplicitamente il tipo dell’interfaccia funzionale.
Errore n. 5: usare un riferimento a un metodo d’istanza senza oggetto.
Per esempio, String::toUpperCase funziona correttamente in map, perché il primo parametro è l’oggetto String stesso. Ma fuori da un contesto adatto, dove ci si aspetta un metodo statico, porterà a un errore.
GO TO FULL VERSION