Beginnen Sie mit Methodenreferenzen in Java

Zusammen mit Lambdas brachte Java SE 8 Methodenverweise auf die Java-Sprache. Dieses Tutorial bietet einen kurzen Überblick über Methodenreferenzen in Java. Anschließend können Sie sie mit Java-Codebeispielen verwenden. Am Ende des Lernprogramms erfahren Sie, wie Sie mithilfe von Methodenreferenzen auf statische Methoden, gebundene und ungebundene nicht statische Methoden und Konstruktoren einer Klasse verweisen und wie Sie auf Instanzmethoden in der Oberklasse und der aktuellen Klasse verweisen Typen. Sie werden auch verstehen, warum viele Java-Entwickler Lambda-Ausdrücke und Methodenreferenzen als sauberere und einfachere Alternative zu anonymen Klassen übernommen haben.

Beachten Sie, dass Codebeispiele in diesem Lernprogramm mit JDK 12 kompatibel sind.

download Code abrufen Laden Sie den Quellcode herunter, zum Beispiel Anwendungen in diesem Tutorial. Erstellt von Jeff Friesen für JavaWorld.

Methodenreferenzen: Ein Primer

In meinem vorherigen Java 101-Tutorial wurden Lambda-Ausdrücke eingeführt, mit denen anonyme Methoden definiert werden, die dann als Instanzen einer funktionalen Schnittstelle behandelt werden können. Manchmal ruft ein Lambda-Ausdruck nur eine vorhandene Methode auf. Das folgende Codefragment verwendet beispielsweise ein Lambda, um System.outdie void println(s)Methode für das einzelne Argument des Lambdas aufzurufen. Der sTyp ist noch nicht bekannt:

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

Das Lambda präsentiert (s)als formale Parameterliste einen Codekörper, dessen System.out.println(s)Ausdruck sden Wert in den Standardausgabestream druckt . Es gibt keinen expliziten Schnittstellentyp. Stattdessen leitet der Compiler aus dem umgebenden Kontext die zu instanziierende Funktionsschnittstelle ab. Betrachten Sie beispielsweise das folgende Codefragment:

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

Der Compiler analysiert die vorherige Deklaration und stellt fest, dass die Methode der java.util.function.Consumervordefinierten Funktionsschnittstelle mit void accept(T t)der formalen Parameterliste ( (s)) des Lambda übereinstimmt . Er bestimmt auch , dass accept()‚s voidRückgabetyp Streichhölzer println()‘ s voidRückgabetyp. Das Lambda ist also gebunden an Consumer.

Insbesondere ist das Lambda an gebunden Consumer. Der Compiler generiert Code, sodass ein Aufruf der Methode von Consumer'dazu void accept(String s)führt, dass das übergebene Zeichenfolgenargument san System.outdie void println(String s)Methode von ' übergeben wird. Dieser Aufruf wird unten gezeigt:

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

Um Tastenanschläge zu speichern, können Sie das Lambda durch eine Methodenreferenz ersetzen , die eine kompakte Referenz auf eine vorhandene Methode ist. Zum Beispiel können die folgenden Code - Fragment ersetzt (String s) -> System.out.println(s)mit System.out::println, wo ::bedeutet , dass System.out‚s void println(String s)Methode verwiesen wird:

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

Es ist nicht notwendig , eine formale Parameterliste für die vorherige Methode Referenz angeben , da der Compiler dieser Liste auf der Grundlage ableiten kann Consumerdie dieser parametrisierte Typ java.lang.Stringtatsächlichen Typargument ersetzt Tin void accept(T t), und ist auch die Art des einzelnen Parameters in dem des Lambda - Körper System.out.println()Methodenaufruf.

Methodenreferenzen im Detail

Eine Methodenreferenz ist eine syntaktische Verknüpfung zum Erstellen eines Lambda aus einer vorhandenen Methode. Anstatt einen Implementierungskörper bereitzustellen, bezieht sich eine Methodenreferenz auf die Methode einer vorhandenen Klasse oder eines vorhandenen Objekts. Wie bei einem Lambda erfordert eine Methodenreferenz einen Zieltyp.

Sie können Methodenreferenzen verwenden, um auf statische Methoden einer Klasse, gebundene und ungebundene nicht statische Methoden und Konstruktoren zu verweisen. Sie können auch Methodenreferenzen verwenden, um auf Instanzmethoden in Oberklassen- und aktuellen Klassentypen zu verweisen. Ich werde Ihnen jede dieser Methodenreferenzkategorien vorstellen und zeigen, wie sie in einer kleinen Demo verwendet werden.

Erfahren Sie mehr über Methodenreferenzen

Lesen Sie nach dem Lesen dieses Abschnitts die Methodenreferenzen in Java 8 (Toby Weston, Februar 2014), um weitere Informationen zu Methodenreferenzen in gebundenen und ungebundenen nicht statischen Methodenkontexten zu erhalten.

Verweise auf statische Methoden

Eine statische Methodenreferenz bezieht sich auf eine statische Methode in einer bestimmten Klasse. Die Syntax lautet , wobei die Klasse und die statische Methode identifiziert werden. Ein Beispiel ist . Listing 1 zeigt eine statische Methodenreferenz.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]); } }

Die main()Methode von Listing 1 sortiert ein Paar von Integer-Arrays über die Methode der java.util.ArraysKlasse static void sort(int[] a), die in statischen Methodenreferenzen und äquivalenten Lambda-Ausdruckskontexten angezeigt wird. Nach dem Sortieren eines Arrays fordruckt eine Schleife den Inhalt des sortierten Arrays in den Standardausgabestream.

Bevor wir eine Methodenreferenz oder ein Lambda verwenden können, muss es an eine funktionale Schnittstelle gebunden sein. Ich verwende die vordefinierte ConsumerFunktionsschnittstelle, die die Anforderungen an Methodenreferenz / Lambda erfüllt. Der Sortiervorgang beginnt , indem das Array vorbei zu sortierenden Consumers - accept()Methode.

Kompilieren Sie Listing 1 ( javac MRDemo.java) und führen Sie die Anwendung ( java MRDemo) aus. Sie werden die folgende Ausgabe beobachten:

2 5 10 17 19 3 4 5 14 19 21

Verweise auf gebundene nicht statische Methoden

Eine gebundene nicht statische Methodenreferenz bezieht sich auf eine nicht statische Methode, die an ein Empfängerobjekt gebunden ist . Die Syntax lautet , wobei der Empfänger und die Instanzmethode identifiziert werden. Ein Beispiel ist . Listing 2 zeigt eine gebundene nicht statische Methodenreferenz.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

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()); } }

Die main()Methode von Listing 2 weist der StringVariablen eine Zeichenfolge zu sund ruft dann die print()Klassenmethode mit Funktionalität auf, um die Länge dieser Zeichenfolge als Argument dieser Methode zu erhalten. print()wird in Methodenreferenz ( s::length- length()ist gebunden an s), äquivalentem Lambda und äquivalenten anonymen Klassenkontexten aufgerufen .

Ich habe die print()Verwendung der java.util.function.Suppliervordefinierten Funktionsschnittstelle definiert, deren get()Methode einen Ergebnislieferanten zurückgibt. In diesem Fall implementiert die an übergebene SupplierInstanz print()ihre get()Rückgabemethode s.length(). print()gibt diese Länge aus.

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;Der Compiler sucht nach einem Konstruktor, der ein StringArgument akzeptiert, da für Functiondie apply()Methode ein einzelnes (in diesem Kontext) StringArgument erforderlich ist . Das Ausführen von function.apply("some name")Ergebnissen "some name"wird an übergeben MRDemo(String name).