W3docs

Références de méthodes Java

Utilisez l'opérateur :: en Java pour référencer des méthodes statiques, d'instance, liées et des constructeurs en tant que lambdas.

Une référence de méthode est une syntaxe abrégée pour un lambda dont le corps ne fait rien d'autre qu'appeler une méthode existante. Lorsqu'un lambda est littéralement x -> SomeClass.foo(x) ou (a, b) -> a.bar(b), l'opérateur :: vous permet de l'écrire comme SomeClass::foo ou Some::bar. Le compilateur produit la même valeur dans les deux cas — une instance de l'interface fonctionnelle appropriée — donc partout où un lambda convient, une référence de méthode correspondante convient aussi.

Function<String, Integer> len1 = s -> s.length();
Function<String, Integer> len2 = String::length;            // identical at runtime

List<String> names = List.of("Bob", "Alice");
names.forEach(s -> System.out.println(s));                   // lambda
names.forEach(System.out::println);                          // method reference

Les quatre formes ci-dessous couvrent toutes les références de méthodes que vous écrirez. La compétence unique consiste à reconnaître quelle forme convient à un site d'appel donné.

Forme 1 : référence de méthode statique — ClassName::staticMethod

La méthode est une méthode static sur une classe. La référence devient un lambda dont les paramètres sont les paramètres de la méthode statique :

Function<String, Integer> parse  = Integer::parseInt;        // s -> Integer.parseInt(s)
BinaryOperator<Integer>   max    = Math::max;                 // (a, b) -> Math.max(a, b)
Function<Object, String>  toStr  = String::valueOf;            // o -> String.valueOf(o)

C'est la forme qui apparaît dans le code de flux comme nums.stream().reduce(0, Integer::sum)Integer.sum(int, int) est statique, donc Integer::sum est un BinaryOperator<Integer> (un BiFunction<Integer, Integer, Integer>).

Forme 2 : référence de méthode d'instance liée — instance::method

La méthode est une méthode d'instance sur un objet spécifique et nommé. La référence devient un lambda dont les paramètres sont les paramètres de la méthode (l'instance est capturée) :

PrintStream out = System.out;
Consumer<String> print = out::println;                  // s -> out.println(s)

String prefix = "Hello, ";
Function<String, String> greet = prefix::concat;        // name -> prefix.concat(name)

List<String> log = new ArrayList<>();
Consumer<String> record = log::add;                     // msg -> log.add(msg)

Le récepteur lié fournit l'emplacement sans argument : parce que prefix est déjà capturé, greet n'a besoin que de l'argument passé à concat, donc c'est une Function<String, String> plutôt qu'une BiFunction. De même, log::add garde log fixe et n'expose que l'élément à ajouter, donnant un Consumer<String>.

L'instance capturée est conservée par l'objet résultant, de la même façon qu'un lambda capture des variables locales effectively final. Les références liées sont la façon de dire "utilise la méthode de cet objet comme rappel" — Logger::info sur un logger particulier, event::handle sur un gestionnaire particulier.

Forme 3 : référence de méthode d'instance non liée — ClassName::method

La méthode est une méthode d'instance, mais vous la référencez par la classe plutôt que par une instance spécifique. La référence devient un lambda dont le premier paramètre est le récepteur, et le reste sont les propres paramètres de la méthode :

Function<String, Integer>   len    = String::length;             // s -> s.length()        — first param is the receiver
Function<String, String>    upper  = String::toUpperCase;         // s -> s.toUpperCase()
BiPredicate<String, String> starts = String::startsWith;          // (s, prefix) -> s.startsWith(prefix)

C'est la forme sur laquelle les gens trébuchent. String::length semble pouvoir signifier "la méthode length sur la classe String" — mais il n'existe pas de telle méthode statique. Cela signifie réellement "étant donné n'importe quelle String, appelle sa méthode d'instance length() — le récepteur est le premier paramètre du lambda." C'est pourquoi String::length est une Function<String, Integer> (une entrée, une sortie) et String::startsWith est un BiPredicate<String, String> (la deuxième entrée est le préfixe que le récepteur teste).

Cette forme est le moteur derrière presque chaque pipeline de flux :

people.stream()
    .map(Person::name)               // unbound: p -> p.name()
    .filter(s -> s.startsWith("A"))
    .map(String::toUpperCase)        // unbound: s -> s.toUpperCase()
    .forEach(System.out::println);   // bound: s -> out.println(s)

Forme 4 : référence de constructeur — ClassName::new

Référence un constructeur comme une fonction. Le lambda résultant prend les paramètres du constructeur et retourne une nouvelle instance :

Supplier<List<String>>           listOf  = ArrayList::new;                  // () -> new ArrayList<>()
Function<Integer, ArrayList<?>>  sized   = ArrayList::new;                   // n -> new ArrayList<>(n)
Function<String, BigDecimal>     toBig   = BigDecimal::new;                  // s -> new BigDecimal(s)
BiFunction<String, Integer, AbstractMap.SimpleEntry<String, Integer>> entry =
    AbstractMap.SimpleEntry::new;

Les références de constructeur permettent à Collectors.toCollection(TreeSet::new) de choisir un type de destination, et à Stream.generate(Random::new) de produire des objets Random indépendants à chaque appel de get().

Les tableaux ont une forme spéciale : String[]::new est une IntFunction<String[]>n -> new String[n]. C'est ce qu'utilise stream.toArray(String[]::new).

Référence de méthode vs lambda — quand chacun l'emporte

Une référence de méthode est le bon choix lorsque le corps du lambda est exactement un seul appel de méthode avec les paramètres passés dans l'ordre :

LambdaRéférence de méthode
s -> s.length()String::length
s -> System.out.println(s)System.out::println
(a, b) -> a.compareTo(b)String::compareTo
() -> new ArrayList<>()ArrayList::new

Un lambda est le bon choix lorsque le corps fait autre chose :

  • Appelle plus d'une méthode : s -> s.trim().toUpperCase() (pas de référence pour la chaîne).
  • A une transformation d'argument : s -> System.out.println("[" + s + "]").
  • A un flux de contrôle : n -> n < 0 ? 0 : n.
  • Réordonne ou duplique les arguments : (a, b) -> b.compareTo(a) (comparateur inversé).

L'optimisation ne concerne pas vraiment la vitesse d'exécution — les deux se compilent vers le même bootstrap invokedynamic. Il s'agit de lisibilité. Person::name saute aux yeux comme "le champ name," alors que p -> p.name() vous oblige à lire trois jetons. Quand la référence convient, préférez-la ; quand ce n'est pas le cas, ne tordez pas le code pour la faire tenir.

Un piège avec les références de constructeur : surcharges ambiguës

ClassName::new fonctionne bien quand il y a un seul constructeur correspondant à l'interface cible. Quand il y en a plusieurs, le compilateur choisit en fonction du nombre de paramètres et des types du type cible. La plupart du temps ça fonctionne ; parfois non, et vous devez disambiguïser en typant la variable explicitement ou en revenant à un lambda :

// ArrayList has constructors: (), (int), (Collection)
Supplier<ArrayList<String>>           a = ArrayList::new;     // picks the no-arg
Function<Integer, ArrayList<String>>  b = ArrayList::new;     // picks the (int) one
Function<List<String>, ArrayList<String>> c = ArrayList::new; // picks the (Collection) one

// var inference can't disambiguate — this would not compile:
// var ambiguous = ArrayList::new;

La solution est de garder le type cible explicite, comme dans a, b, c ci-dessus.

Un exemple complet : les quatre formes en un seul programme

Le programme ci-dessous construit et utilise une référence de méthode de chaque forme, démontre comment String::length (non lié) devient une Function<String, Integer>, et montre l'astuce de référence de constructeur qui alimente stream().toArray(T[]::new).

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Les quatre formes se compilent en instances d'interfaces fonctionnelles ordinaires — parse est une Function<String, Integer> que vous ayez écrit s -> Integer.parseInt(s) ou Integer::parseInt. L'abréviation est purement syntaxique.
  • Les références non liées String::length et String::toUpperCase ont toutes deux un récepteur comme premier paramètre. C'est pourquoi String::length est une Function<String, Integer> et String::startsWith est un BiPredicate<String, String> — le récepteur est un emplacement, le paramètre explicite l'autre.
  • La référence de constructeur String[]::new a produit une IntFunction<String[]> — la forme qu'attend stream().toArray(...). Les références de constructeur permettent d'indiquer à un flux "voici le type de destination."
  • Le comparateur de longueur inversée ne pouvait pas être écrit comme une référence de méthode : le récepteur et le paramètre s'échangent, et les références de méthodes ne peuvent pas réordonner les arguments. C'est exactement le type de cas où un lambda reste le bon choix.

La suite

Vous pouvez maintenant écrire un pipeline de flux presque entièrement en références de méthodes et laisser les quelques transformations qui nécessitent vraiment une mise en forme vivre dans de petits lambdas. Ce style est la transition naturelle vers le point central de cette partie : les flux. Le prochain chapitre, Introduction aux flux Java, présente l'API Stream<T> — ce qu'elle est, à quoi ressemble un pipeline de flux, pourquoi il est paresseux, pourquoi il ne peut être utilisé qu'une seule fois, et comment il s'articule avec les lambdas, interfaces fonctionnelles et références de méthodes que vous venez d'apprendre.

Entraînement

Pratique
`String::length` est quel type de référence de méthode, et quelle interface fonctionnelle correspond ?
`String::length` est quel type de référence de méthode, et quelle interface fonctionnelle correspond ?
Was this page helpful?