Premiers pas avec les références de méthodes en Java

Avec les lambdas, Java SE 8 a apporté des références de méthode au langage Java. Ce didacticiel offre un bref aperçu des références de méthodes en Java, puis vous permet de commencer à les utiliser avec des exemples de code Java. À la fin du didacticiel, vous saurez comment utiliser des références de méthode pour faire référence aux méthodes statiques d'une classe, aux méthodes non statiques liées et indépendantes et aux constructeurs, ainsi que comment les utiliser pour faire référence aux méthodes d'instance dans la superclasse et la classe actuelle les types. Vous comprendrez également pourquoi de nombreux développeurs Java ont adopté les expressions lambda et les références de méthodes comme une alternative plus propre et plus simple aux classes anonymes.

Notez que les exemples de code de ce didacticiel sont compatibles avec JDK 12.

télécharger Obtenir le code Téléchargez le code source des exemples d'applications dans ce didacticiel. Créé par Jeff Friesen pour JavaWorld.

Références de méthode: une amorce

Mon précédent didacticiel Java 101 présentait les expressions lambda, qui sont utilisées pour définir des méthodes anonymes qui peuvent ensuite être traitées comme des instances d'une interface fonctionnelle. Parfois, une expression lambda ne fait rien de plus que d'appeler une méthode existante. Par exemple, le fragment de code suivant utilise un lambda pour appeler System.outla void println(s)méthode de sur l'argument unique de lambda - le stype de celui-ci n'est pas encore connu:

(s) -> System.out.println(s)

Le lambda se présente (s)comme sa liste de paramètres formels et un corps de code dont l' System.out.println(s)expression imprime sla valeur de s dans le flux de sortie standard. Il n'a pas de type d'interface explicite. Au lieu de cela, le compilateur déduit du contexte environnant quelle interface fonctionnelle instancier. Par exemple, considérez le fragment de code suivant:

Consumer consumer = (s) -> System.out.println(s);

Le compilateur analyse la déclaration précédente et détermine que la méthode de java.util.function.Consumerl'interface fonctionnelle prédéfinie void accept(T t)correspond à la liste de paramètres formels de lambda ( (s)). Il détermine également que accept()le voidtype de retour correspond au type println()de voidretour de. Le lambda est donc lié à Consumer.

Plus précisément, le lambda est lié à Consumer. Le compilateur génère du code pour qu'un appel de Consumerla void accept(String s)méthode de s entraîne le passage de l'argument de chaîne sà System.outla void println(String s)méthode de. Cet appel est illustré ci-dessous:

consumer.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

Pour enregistrer les frappes, vous pouvez remplacer le lambda par une référence de méthode , qui est une référence compacte à une méthode existante. Par exemple, le fragment de code suivant remplace (String s) -> System.out.println(s)par System.out::println, où ::signifie que System.outla void println(String s)méthode de s est référencée:

Consumer consumer2 = System.out::println; // The method reference is shorter. consumer2.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

Il n'est pas nécessaire de spécifier une liste de paramètres formelle pour la référence de méthode précédente car le compilateur peut déduire cette liste en fonction de Consumerl' java.lang.Stringargument de type réel de ce type paramétré remplace Tdans void accept(T t), et est également le type du paramètre unique dans l' System.out.println()appel de méthode du corps lambda .

Références de méthode en profondeur

Une référence de méthode est un raccourci syntaxique pour créer un lambda à partir d'une méthode existante. Au lieu de fournir un corps d'implémentation, une référence de méthode fait référence à la méthode d'une classe ou d'un objet existant. Comme avec un lambda, une référence de méthode nécessite un type cible.

Vous pouvez utiliser des références de méthode pour faire référence aux méthodes statiques d'une classe, aux méthodes non statiques liées et indépendantes et aux constructeurs. Vous pouvez également utiliser des références de méthode pour faire référence aux méthodes d'instance dans les types de classe superclasse et courants. Je vais vous présenter chacune de ces catégories de référence de méthode et montrer comment elles sont utilisées dans une petite démo.

En savoir plus sur les références de méthodes

Après avoir lu cette section, consultez Références de méthodes dans Java 8 (Toby Weston, février 2014) pour plus d'informations sur les références de méthodes dans des contextes de méthodes non statiques liés et non liés.

Références aux méthodes statiques

Une référence de méthode statique fait référence à une méthode statique dans une classe spécifique. Sa syntaxe est , où identifie la classe et identifie la méthode statique. Un exemple est . Le listing 1 montre une référence de méthode statique.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

Listing 1. MRDemo.java (version 1)

import java.util.Arrays; import java.util.function.Consumer; public class MRDemo { public static void main(String[] args) { int[] array = { 10, 2, 19, 5, 17 }; Consumer consumer = Arrays::sort; consumer.accept(array); for (int i = 0; i < array.length; i++) System.out.println(array[i]); System.out.println(); int[] array2 = { 19, 5, 14, 3, 21, 4 }; Consumer consumer2 = (a) -> Arrays.sort(a); consumer2.accept(array2); for (int i = 0; i < array2.length; i++) System.out.println(array2[i]); } }

La main()méthode du listing 1 trie une paire de tableaux d'entiers via la méthode de la java.util.Arraysclasse static void sort(int[] a), qui apparaît dans la référence de méthode statique et les contextes d'expression lambda équivalents. Après avoir trié un tableau, une forboucle imprime le contenu du tableau trié dans le flux de sortie standard.

Before we can use a method reference or a lambda, it must be bound to a functional interface. I'm using the predefined Consumer functional interface, which meets the method reference/lambda requirements. The sort operation commences by passing the array to be sorted to Consumer's accept() method.

Compile Listing 1 (javac MRDemo.java) and run the application (java MRDemo). You'll observe the following output:

2 5 10 17 19 3 4 5 14 19 21

References to bound non-static methods

A bound non-static method reference refers to a non-static method that's bound to a receiver object. Its syntax is objectName::instanceMethodName, where objectName identifies the receiver and instanceMethodName identifies the instance method. An example is s::trim. Listing 2 demonstrates a bound non-static method reference.

Listing 2. MRDemo.java (version 2)

import java.util.function.Supplier; public class MRDemo { public static void main(String[] args) { String s = "The quick brown fox jumped over the lazy dog"; print(s::length); print(() -> s.length()); print(new Supplier() { @Override public Integer get() { return s.length(); // closes over s } }); } public static void print(Supplier supplier) { System.out.println(supplier.get()); } }

Listing 2's main() method assigns a string to String variable s and then invokes the print() class method with functionality to obtain this string's length as this method's argument. print() is invoked in method reference (s::length -- length() is bound to s), equivalent lambda, and equivalent anonymous class contexts.

I've defined print() to use the java.util.function.Supplier predefined functional interface, whose get() method returns a supplier of results. In this case, the Supplier instance passed to print() implements its get() method to return s.length(); print() outputs this length.

s::length introduces a closure that closes over s. You can see this more clearly in the lambda example. Because the lambda has no arguments, the value of s is only available from the enclosing scope. Therefore, the lambda body is a closure that closes over s. The anonymous class example makes this even clearer.

Compile Listing 2 and run the application. You'll observe the following output:

44 44 44

References to unbound non-static methods

An unbound non-static method reference refers to a non-static method that's not bound to a receiver object. Its syntax is className::instanceMethodName, where className identifies the class that declares the instance method and instanceMethodName identifies the instance method. An example is String::toLowerCase.

String::toLowerCase is an unbound non-static method reference that identifies the non-static String toLowerCase() method of the String class. However, because a non-static method still requires a receiver object (in this example a String object, which is used to invoke toLowerCase() via the method reference), the receiver object is created by the virtual machine. toLowerCase() will be invoked on this object. String::toLowerCase specifies a method that takes a single String argument, which is the receiver object, and returns a String result. String::toLowerCase() is equivalent to lambda (String s) -> { return s.toLowerCase(); }.

Listing 3 demonstrates this unbound non-static method reference.

Listing 3. MRDemo.java (version 3)

import java.util.function.Function; public class MRDemo { public static void main(String[] args) { print(String::toLowerCase, "STRING TO LOWERCASE"); print(s -> s.toLowerCase(), "STRING TO LOWERCASE"); print(new Function() { @Override public String apply(String s) // receives argument in parameter s; { // doesn't need to close over s return s.toLowerCase(); } }, "STRING TO LOWERCASE"); } public static void print(Function function, String s) { System.out.println(function.apply(s)); } }

Listing 3's main() method invokes the print() class method with functionality to convert a string to lowercase and the string to be converted as the method's arguments. print() is invoked in method reference (String::toLowerCase, where toLowerCase() isn't bound to a user-specified object) and equivalent lambda and anonymous class contexts.

I've defined print() to use the java.util.function.Function predefined functional interface, which represents a function that accepts one argument and produces a result. In this case, the Function instance passed to print() implements its R apply(T t) method to return s.toLowerCase(); print() outputs this string.

Although the String part of String::toLowerCase makes it look like a class is being referenced, only an instance of this class is referenced. The anonymous class example makes this more obvious. Note that in the anonymous class example the lambda receives an argument; it doesn't close over parameter s (i.e., it's not a closure).

Compile Listing 3 and run the application. You'll observe the following output:

string to lowercase string to lowercase string to lowercase

References to constructors

You can use a method reference to refer to a constructor without instantiating the named class. This kind of method reference is known as a constructor reference. Its syntax is className::new. className must support object creation; it cannot name an abstract class or interface. Keyword new names the referenced constructor. Here are some examples:

  • Character::new: equivalent to lambda (Character ch) -> new Character(ch)
  • Long::new: equivalent to lambda (long value) -> new Long(value) or (String s) -> new Long(s)
  • ArrayList::new: equivalent to lambda () -> new ArrayList()
  • float[]::new: equivalent to lambda (int size) -> new float[size]

The last constructor reference example specifies an array type instead of a class type, but the principle is the same. The example demonstrates an array constructor reference to the "constructor" of an array type.

To create a constructor reference, specify new without a constructor. When a class such as java.lang.Long declares multiple constructors, the compiler compares the functional interface's type against all of the constructors and chooses the best match. Listing 4 demonstrates a constructor reference.

Listing 4. MRDemo.java (version 4)

import java.util.function.Supplier; public class MRDemo { public static void main(String[] args) { Supplier supplier = MRDemo::new; System.out.println(supplier.get()); } }

Listing 4's MRDemo::new constructor reference is equivalent to lambda () -> new MRDemo(). Expression supplier.get() executes this lambda, which invokes MRDemo's default no-argument constructor and returns the MRDemo object, which is passed to System.out.println(). This method converts the object to a string, which it prints.

Now suppose you have a class with a no-argument constructor and a constructor that takes an argument, and you want to call the constructor that takes an argument. You can accomplish this task by choosing a different functional interface, such as the predefined Function interface shown in Listing 5.

Listing 5. MRDemo.java (version 5)

import java.util.function.Function; public class MRDemo { private String name; MRDemo() { name = ""; } MRDemo(String name) { this.name = name; System.out.printf("MRDemo(String name) called with %s%n", name); } public static void main(String[] args) { Function function = MRDemo::new; System.out.println(function.apply("some name")); } }

Function function = MRDemo::new;oblige le compilateur à rechercher un constructeur qui prend un Stringargument, car Functionla apply()méthode de s nécessite un seul Stringargument (dans ce contexte) . L'exécution function.apply("some name")entraîne la "some name"transmission à MRDemo(String name).