Les références de méthode en Java servent surtout à rendre un code plus lisible quand une lambda ne fait que déléguer vers une méthode déjà existante. On y gagne en clarté dans les streams, les tris, les callbacks et les opérations courantes du package java.util.function. Dans cet article, je passe en revue la syntaxe, les quatre formes, les cas où cette écriture aide vraiment et les pièges qui la rendent moins utile qu’une simple lambda.
Les points clés à garder en tête
- Une référence de méthode est un raccourci lisible pour une lambda qui appelle simplement une méthode déjà existante.
- Elle dépend toujours d’une interface fonctionnelle, c’est-à-dire une interface avec une seule méthode abstraite.
- Il existe quatre formes à connaître: méthode statique, méthode d’un objet particulier, méthode d’un objet arbitraire et constructeur.
- Je la trouve particulièrement utile dans les tris, les streams, les transformations simples et les callbacks sans logique additionnelle.
- Dès qu’il faut ajouter une condition, une transformation ou du contexte, la lambda redevient souvent le meilleur choix.
Pourquoi cette écriture améliore souvent la lecture du code
La vraie valeur de cette syntaxe, ce n’est pas de gagner quelques caractères. C’est de faire comprendre plus vite l’intention: je ne fais qu’appeler une méthode connue. Quand je lis une lambda comme (a, b) -> String::compareToIgnoreCase ou, plus simplement, une référence adaptée au contexte, je sais immédiatement qu’il n’y a pas de logique cachée, juste un passage de relais.
Je la préfère quand le code devient plus net sans perdre d’information. À l’inverse, si la forme compacte masque le comportement réel, je reviens sans hésiter à une lambda. Le gain est donc surtout éditorial; côté exécution, la différence est généralement négligeable pour un usage courant.
| Situation | Lambda | Référence | Mon choix |
|---|---|---|---|
| Appel direct d’une méthode existante | x -> traiter(x) |
Classe::traiter |
Référence de méthode |
| Besoin d’ajouter une condition ou une transformation | x -> x == null ? ... : ... |
Peu adapté | Lambda |
| Intention métier déjà claire dans le nom de la méthode | Possible | Très lisible | Référence de méthode |
Autrement dit, je ne cherche pas à faire du code plus court pour le principe. Je cherche à faire du code plus vite scannable, surtout dans les portions où la logique se contente d’appeler une méthode de confiance. Pour bien l’utiliser, il faut maintenant distinguer les quatre formes syntaxiques qui reviennent vraiment en pratique.
Les quatre formes à connaître
La documentation Java classe ces références en quatre familles. C’est une séparation simple, mais elle évite beaucoup d’hésitations au moment de lire ou d’écrire le code.
| Forme | Syntaxe | Exemple | Quand je l’utilise |
|---|---|---|---|
| Méthode statique | Classe::methodeStatique |
Integer::parseInt |
Quand la logique existe déjà dans une fonction utilitaire |
| Méthode d’un objet particulier | instance::methodeInstance |
System.out::println |
Quand un objet précis doit recevoir l’appel |
| Méthode d’un objet arbitraire d’un type donné | Type::methodeInstance |
String::compareToIgnoreCase |
Quand chaque élément du flux appelle sa propre méthode |
| Constructeur | Type::new |
HashSet::new |
Quand il faut fabriquer une nouvelle instance à la demande |
Méthode statique
Une référence comme Integer::parseInt ou Math::abs fonctionne quand la méthode est déjà disponible au niveau de la classe. Je m’en sers surtout quand le nom de la méthode dit exactement ce que le code doit faire, sans couche intermédiaire inutile.
Méthode d’un objet particulier
System.out::println est l’exemple le plus parlant. Ici, ce n’est pas la classe qui compte, mais l’instance System.out elle-même. C’est utile quand l’objet appelé est déjà connu et que la méthode reste une action simple, comme afficher une valeur ou envoyer un message à un composant précis.
Méthode d’un objet arbitraire d’un type donné
String::compareToIgnoreCase est la forme qui surprend le plus au début. Mentalement, il faut la lire comme: “pour chaque objet de type String, appelle sa méthode compareToIgnoreCase”. Cette forme est très élégante dans les tris et les opérations de comparaison parce qu’elle évite de réécrire (a, b) -> a.compareToIgnoreCase(b).
Lire aussi : À quoi sert Java ? Usages réels et pertinence en 2026
Constructeur
HashSet::new ou ArrayList::new servent quand le code attend une fabrique d’objets. C’est fréquent avec Supplier, avec certains collecteurs ou avec des APIs qui construisent paresseusement leurs résultats. Là encore, la valeur vient de la lisibilité: je vois tout de suite qu’une nouvelle instance sera créée sans logique supplémentaire.
Une fois ces quatre formes en tête, le vrai sujet devient plus concret: quel type cible attend la méthode qui reçoit cette référence, et comment le compilateur relie les deux. C’est là que beaucoup de confusions apparaissent.
Ce que le type cible change réellement
Une référence de méthode n’existe pas toute seule dans le vide. Elle a besoin d’un type cible, le plus souvent une interface fonctionnelle: Predicate, Function, Supplier, Consumer, Comparator, BiFunction, etc. L’idée est simple: l’interface définit une seule méthode abstraite, et la référence doit pouvoir se brancher dessus avec une signature compatible.
Predicate vide = String::isEmpty;
Function nettoyer = String::trim;
Supplier> fabrique = HashSet::new; Dans ces exemples, le compilateur déduit ce qu’il attend à partir du contexte. String::isEmpty correspond à une fonction qui prend une chaîne et renvoie un booléen. String::trim prend une chaîne et renvoie une chaîne. HashSet::new ne prend aucun argument et renvoie un nouvel objet. Si la signature ne colle pas, la compilation échoue immédiatement, ce qui est sain: le problème est détecté avant l’exécution.
Je vois aussi des cas où la résolution devient moins évidente à cause d’une surcharge ou d’un générique trop large. Dans ce cas, une lambda explicite ou un cast de type peut être plus lisible qu’une référence qui oblige à deviner l’intention. La règle pratique est simple: si le type cible n’est pas évident à la première lecture, la syntaxe compacte a déjà perdu une partie de son intérêt.
Quand ce mécanisme est clair, on peut enfin regarder les cas d’usage qui apportent le plus de valeur dans du code réel, surtout autour des flux et des collections.
Les usages qui apportent le plus de valeur dans les streams et les collections
Je retrouve les références de méthode là où le code enchaîne des opérations simples: transformation, filtrage, affichage, tri, création d’objets. C’est précisément dans ce genre de pipeline qu’une lambda verbeuse finit par alourdir la lecture.
noms.stream()
.map(String::trim)
.filter(chaine -> !chaine.isEmpty())
.forEach(System.out::println);Dans ce petit pipeline, String::trim clarifie la transformation, et System.out::println signale qu’on termine par un simple affichage. Je garde la lambda pour le filtrage, parce que chaine -> !chaine.isEmpty() exprime encore mieux l’intention qu’une astuce artificielle. Le point important n’est pas d’utiliser des références partout, mais de les employer là où elles réduisent vraiment le bruit visuel.
utilisateurs.sort(String::compareToIgnoreCase);
Set index = flux.collect(Collectors.toCollection(HashSet::new)); Le premier exemple est bon pour les tris lexicaux. Le second montre une fabrique d’objets passée à un collecteur, ce qui est un cas très fréquent dans le code applicatif. Dans les deux situations, la référence de méthode remplace une lambda qui n’aurait apporté aucune information supplémentaire.
Il y a aussi un avantage moins visible mais important: ces formes se marient bien avec les APIs du JDK, parce qu’elles collent à l’idée de contrat fonctionnel. Quand le code suit cette logique, il reste cohérent d’une classe à l’autre, ce qui facilite la relecture et la maintenance. Mais cette cohérence tombe vite si on force la syntaxe dans des cas qui ne s’y prêtent pas.
Les erreurs qui font perdre l’intérêt de cette forme
Le premier piège, c’est de croire qu’une syntaxe plus courte est forcément meilleure. Ce n’est pas vrai. Si la méthode appelée ne dit pas assez de choses au lecteur, ou si l’appel est trop indirect, une lambda explicite restera plus honnête.
- Forcer une référence quand il faut ajouter de la logique : dès qu’il y a une condition, un calcul, un logging ou une transformation d’arguments, la lambda est souvent plus claire.
- Confondre le bon type cible : la signature de la méthode doit correspondre au contrat attendu par l’interface fonctionnelle.
- Ignorer les surcharges : certaines méthodes existent en plusieurs versions, et la lecture devient moins évidente si le contexte n’est pas assez précis.
- Choisir la brièveté au détriment du sens : une référence qui oblige à connaître toute l’API par cœur n’aide pas toujours un collègue qui découvre le code.
- Utiliser une forme statique là où un objet particulier est nécessaire : la différence entre classe, instance et type arbitraire compte vraiment.
Un exemple simple me paraît parlant: si je dois écrire x -> logger.info(prefix + x), je ne vais pas chercher à le “convertir” en référence de méthode. Ici, la logique métier ne se réduit pas à un appel direct, donc la lambda reste la version la plus lisible. Même remarque quand il faut réordonner des arguments ou protéger une valeur nulle: la référence n’exprime pas ce travail supplémentaire.
Quand je code ou que je relis une PR, je me pose donc une question très concrète: est-ce que la forme compacte rend la ligne plus facile à comprendre, ou est-ce qu’elle masque une intention qu’une lambda montrerait mieux ? Cette question mène naturellement à une règle de décision très simple.
La règle simple que j’applique dans un vrai projet Java
Ma règle tient en une phrase: si la lambda ne fait qu’appeler une méthode existante, je teste la référence de méthode; sinon, je garde la lambda. Cette approche évite deux excès opposés: le code trop bavard d’un côté, et le code trop “malin” de l’autre.
- Si la logique se résume à un appel direct, la référence est souvent plus propre.
- Si je dois transformer des données, ajouter un test ou rendre le flux plus explicite, je laisse la lambda.
- Si le lecteur doit connaître trop d’API pour comprendre la ligne, je privilégie la forme la plus lisible, pas la plus courte.
- Si le nom de la méthode raconte déjà exactement l’intention métier, la référence fonctionne très bien.
En pratique, je traite cette syntaxe comme un outil de clarté, pas comme un effet de style. Bien employée, elle allège les pipelines Java et les callbacks simples; mal placée, elle donne juste une impression de modernité sans aider la maintenance. C’est pour cette raison que je la réserve aux endroits où elle rend la lecture plus directe, pas aux endroits où elle cherche seulement à impressionner.