1. Introduction
Method reference (ou en français — « référence de méthode ») est une syntaxe spéciale en Java qui permet de transmettre une méthode (ou un constructeur) existante comme implémentation d’une interface fonctionnelle. On peut « passer » une méthode là où une expression lambda est attendue, si les signatures coïncident.
Syntaxe :
Classe::méthode
objet::méthode
Classe::new
Si une lambda est une mini‑fonction « à la volée », alors une référence de méthode, c’est simplement « passe une méthode déjà existante ». C’est comme, au lieu de réécrire une recette, donner le lien vers la page qui la contient.
Exemple simple
Au lieu de ceci :
list.forEach(s -> System.out.println(s));
On peut faire ainsi :
list.forEach(System.out::println);
C’est concis, n’est-ce pas ?
2. Types de références de méthode
Les références de méthode existent sous quatre formes principales. Les voici — et rien de plus n’est nécessaire.
Référence à une méthode statique
Syntaxe : Classe::methodeStatique
Exemple :
Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"
La même chose avec une lambda :
Function<Integer, String> intToString = i -> String.valueOf(i);
Référence à une méthode d’instance d’un objet
Syntaxe : objet::méthode
Exemple :
PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("Bonjour, le monde!");
Équivalent à une lambda :
Consumer<String> consumer = s -> printer.println(s);
Référence à une méthode d’instance de classe
Syntaxe : Classe::méthode
Ici, le premier paramètre de l’interface fonctionnelle devient l’objet sur lequel la méthode est appelée.
Exemple :
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4
Ici, String::length se transforme en fonction : (String s) -> s.length()
Référence à un constructeur
Syntaxe : Classe::new
Exemple :
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
Équivalent à une lambda :
Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();
3. Quand utiliser une référence de méthode ?
La référence de méthode est pratique lorsque la lambda ne fait qu’appeler une méthode existante sans logique supplémentaire — le code devient alors plus court et plus lisible.
Exemple : tri d’une liste
Au lieu de ceci :
List<String> names = Arrays.asList("John", "Peter", "Anna");
names.sort((a, b) -> a.compareToIgnoreCase(b));
On peut écrire :
names.sort(String::compareToIgnoreCase);
Exemple : traitement des collections
Au lieu de :
list.forEach(s -> System.out.println(s));
Encore plus court :
list.forEach(System.out::println);
Exemple : transformation d’éléments
Au lieu de :
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
On peut :
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Vous en saurez plus sur le Stream API et la classe utilitaire Collectors au niveau 30 :P
4. Comparaison entre références de méthode et expressions lambda
Équivalence
La référence de méthode et l’expression lambda sont souvent interchangeables. Les deux réalisent une interface fonctionnelle si les signatures coïncident.
Exemple :
Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;
Quand vaut-il mieux utiliser une référence de méthode ?
- Quand la lambda ne fait qu’appeler une méthode existante sans logique supplémentaire.
- Pour améliorer la lisibilité, surtout dans de longues chaînes d’appels.
- Quand on veut montrer explicitement : « ici, c’est juste un appel de méthode ».
Quand la référence de méthode ne convient-elle pas ?
- Si une logique supplémentaire est nécessaire (validation, conditions, gestion d’erreurs).
- Si les paramètres doivent être transformés avant l’appel de la méthode.
Exemple :
list.forEach(s -> {
if (s != null) System.out.println(s);
});
// Ici, une référence de méthode ne convient pas, seule une lambda.
5. Pratique : réécrire des expressions lambda à l’aide des références de méthode
Exemple 1 : afficher les noms des animaux
List<String> animals = Arrays.asList("Chat", "Chien", "Perroquet");
animals.forEach(animal -> System.out.println(animal));
Devient :
animals.forEach(System.out::println);
Exemple 2 : convertir des chaînes en nombres
List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Devient :
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Exemple 3 : trier des objets par nom
List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));
Devient :
animalList.sort(Comparator.comparing(Animal::getName));
Ici, Animal::getName est une référence à une méthode d’instance de classe.
Exemple 4 : créer des objets via un constructeur
Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();
Devient :
Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();
6. Comment fonctionne la correspondance des signatures
Une référence de méthode ne peut être utilisée que lorsque la signature de la méthode correspond à celle de la méthode abstraite de l’interface fonctionnelle.
@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
}
}
Ici, Demo::intToHex convient, car il prend un int et retourne un String.
7. Références de méthode et constructeurs avec paramètres
Si le constructeur prend des paramètres, on peut quand même utiliser une référence de méthode — à condition que les signatures coïncident.
@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("Buddy");
System.out.println(cat.getName()); // Buddy
8. Erreurs typiques lors de l’utilisation des références de méthode
Erreur n° 1 : incompatibilité des signatures.
Si la signature de la méthode ne correspond pas à celle de la méthode abstraite de l’interface, le compilateur signalera une erreur. Par exemple, l’interface attend deux paramètres, mais la référence pointe vers une méthode avec un seul paramètre.
Erreur n° 2 : tentative d’utiliser une référence à une méthode d’instance de classe sans objet.
Lorsque vous utilisez la forme Classe::méthode, le premier paramètre de l’interface devient l’objet d’appel. Si vous confondez le nombre ou l’ordre des paramètres, vous obtiendrez une erreur de correspondance.
Erreur n° 3 : utiliser une référence de méthode là où une logique supplémentaire est nécessaire.
S’il faut une condition, un logging ou la gestion d’exceptions, utilisez une lambda, pas une référence de méthode.
Erreur n° 4 : références à des méthodes surchargées.
Si une classe possède plusieurs méthodes portant le même nom, le compilateur peut ne pas savoir laquelle choisir. Parfois, indiquer explicitement le type de l’interface fonctionnelle aide.
Erreur n° 5 : utiliser une référence à une méthode d’instance sans objet.
Par exemple, String::toUpperCase fonctionne correctement dans un map, car le premier paramètre est l’objet String lui‑même. Mais hors du contexte approprié, où un appel statique est attendu, cela mènera à une erreur.
GO TO FULL VERSION