Inizia con i riferimenti ai metodi in Java

Insieme a lambda, Java SE 8 ha portato riferimenti al metodo al linguaggio Java. Questo tutorial offre una breve panoramica dei riferimenti ai metodi in Java, quindi ti consente di iniziare a utilizzarli con esempi di codice Java. Alla fine del tutorial saprai come utilizzare i riferimenti ai metodi per fare riferimento ai metodi statici di una classe, ai metodi non statici associati e non associati e ai costruttori, nonché come usarli per fare riferimento ai metodi di istanza nella superclasse e nella classe corrente tipi. Capirai anche perché molti sviluppatori Java hanno adottato espressioni lambda e riferimenti a metodi come un'alternativa più pulita e più semplice alle classi anonime.

Nota che gli esempi di codice in questo tutorial sono compatibili con JDK 12.

download Ottieni il codice Scarica il codice sorgente per applicazioni di esempio in questo tutorial. Creato da Jeff Friesen per JavaWorld.

Riferimenti al metodo: un primer

Il mio precedente tutorial su Java 101 ha introdotto espressioni lambda, che vengono utilizzate per definire metodi anonimi che possono quindi essere trattati come istanze di un'interfaccia funzionale. A volte, un'espressione lambda non fa altro che chiamare un metodo esistente. Ad esempio, il seguente frammento di codice utilizza un lambda per invocare System.outil void println(s)metodo sul singolo argomento lambda s- il tipo di non è ancora noto:

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

Il lambda presenta (s)come suo elenco di parametri formali e un corpo del codice la cui System.out.println(s)espressione stampa sil valore nel flusso di output standard. Non ha un tipo di interfaccia esplicito. Invece, il compilatore deduce dal contesto circostante quale interfaccia funzionale istanziare. Ad esempio, considera il seguente frammento di codice:

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

Il compilatore analizza la dichiarazione precedente e determina che il metodo java.util.function.Consumerdell'interfaccia funzionale predefinita void accept(T t)corrisponde all'elenco dei parametri formali lambda ( (s)). Determina inoltre che accept()il voidtipo restituito di corrisponde println()al voidtipo restituito. Il lambda è quindi vincolato a Consumer.

Più specificamente, il lambda è associato a Consumer. Il compilatore genera codice in modo che un'invocazione Consumer's void accept(String s)metodo comporta l'argomento stringa passata sstata passata System.out' s void println(String s)metodo. Questa invocazione è mostrata di seguito:

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

Per salvare le sequenze di tasti, puoi sostituire lambda con un riferimento al metodo , che è un riferimento compatto a un metodo esistente. Ad esempio, i seguenti sostituisce frammento di codice (String s) -> System.out.println(s)con System.out::println, dove ::significa che System.out's void println(String s)metodo viene fatto riferimento:

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

Non è necessario specificare un elenco di parametri formali per il riferimento al metodo precedente perché il compilatore può dedurre questo elenco in base all'argomento di tipo effettivo Consumerdi questo tipo parametrizzato java.lang.Stringsostituito Tin void accept(T t)ed è anche il tipo del singolo parametro nella System.out.println()chiamata al metodo del corpo lambda .

Riferimenti di metodo in profondità

Un riferimento al metodo è una scorciatoia sintattica per creare un lambda da un metodo esistente. Invece di fornire un corpo di implementazione, un riferimento al metodo fa riferimento al metodo di una classe o di un oggetto esistente. Come con un lambda, un riferimento al metodo richiede un tipo di destinazione.

È possibile utilizzare riferimenti a metodi per fare riferimento a metodi statici di una classe, metodi non statici associati e non associati e costruttori. È inoltre possibile utilizzare riferimenti a metodi per fare riferimento a metodi di istanza nella superclasse e nei tipi di classe correnti. Ti presenterò ciascuna di queste categorie di riferimento del metodo e mostrerò come vengono utilizzate in una piccola demo.

Ulteriori informazioni sui riferimenti ai metodi

Dopo aver letto questa sezione, controlla i riferimenti ai metodi in Java 8 (Toby Weston, febbraio 2014) per ulteriori informazioni sui riferimenti ai metodi in contesti di metodi non statici associati e non associati.

Riferimenti a metodi statici

Un riferimento a un metodo statico fa riferimento a un metodo statico in una classe specifica. La sua sintassi è , dove identifica la classe e identifica il metodo statico. Un esempio è . Il listato 1 mostra un riferimento al metodo statico.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

Listato 1. MRDemo.java (versione 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]); } }

Il main()metodo del listato 1 ordina una coppia di array di interi tramite il metodo java.util.Arraysdella classe static void sort(int[] a), che appare nel riferimento al metodo statico e nei contesti di espressioni lambda equivalenti. Dopo aver ordinato un array, un forciclo stampa il contenuto dell'array ordinato nel flusso di output standard.

Prima di poter utilizzare un riferimento al metodo o un lambda, è necessario associarlo a un'interfaccia funzionale. Sto usando l' Consumerinterfaccia funzionale predefinita , che soddisfa i requisiti di riferimento del metodo / lambda. Le effettua un ciclo di ordinamento facendo passare la matrice da smistare a Consumer's accept()metodo.

Compila il Listato 1 ( javac MRDemo.java) ed esegui application ( java MRDemo). Osserverai il seguente output:

2 5 10 17 19 3 4 5 14 19 21

Riferimenti a metodi non statici associati

Un riferimento a un metodo non statico associato fa riferimento a un metodo non statico associato a un oggetto ricevitore . La sua sintassi è , dove identifica il destinatario e identifica il metodo di istanza. Un esempio è . Il Listato 2 mostra un riferimento a un metodo non statico associato.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

Listato 2. MRDemo.java (versione 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()); } }

Il main()metodo del Listato 2 assegna una stringa a Stringvariable se quindi invoca il print()metodo di classe con funzionalità per ottenere la lunghezza di questa stringa come argomento di questo metodo. print()viene richiamato nel riferimento al metodo ( s::length- length()è associato a s), lambda equivalente e contesti di classi anonimi equivalenti.

Ho definito print()di utilizzare l' java.util.function.Supplierinterfaccia funzionale predefinita, il cui get()metodo restituisce un fornitore di risultati. In questo caso, l' Supplieristanza passata a print()implementa il suo get()metodo da restituire s.length(); print()restituisce questa lunghezza.

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;fa sì che il compilatore cerchi un costruttore che accetta un Stringargomento, perché Functionil apply()metodo di richiede un singolo (in questo contesto) Stringargomento. L'esecuzione dei function.apply("some name")risultati "some name"viene trasmessa a MRDemo(String name).