CodeGym /Corsi /JAVA 25 SELF /Method references (::): riferimenti ai metodi

Method references (::): riferimenti ai metodi

JAVA 25 SELF
Livello 21 , Lezione 1
Disponibile

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.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION