Zacznij od odwołań do metod w języku Java

Wraz z lambdami Java SE 8 wprowadziła odwołania do metod w języku Java. Ten samouczek zawiera krótkie omówienie odwołań do metod w języku Java, a następnie umożliwia rozpoczęcie korzystania z nich z przykładami kodu Java. Pod koniec samouczka będziesz wiedział, jak używać odwołań do metod w celu odwoływania się do metod statycznych klasy, związanych i niezwiązanych metod niestatycznych oraz konstruktorów, a także jak używać ich do odwoływania się do metod instancji w nadklasie i bieżącej klasie. rodzaje. Zrozumiesz również, dlaczego wielu programistów Java przyjęło wyrażenia lambda i odwołania do metod jako czystszą i prostszą alternatywę dla klas anonimowych.

Zwróć uwagę, że przykłady kodu w tym samouczku są zgodne z JDK 12.

pobierz Pobierz kod Pobierz kod źródłowy na przykład aplikacje w tym samouczku. Stworzone przez Jeffa Friesena dla JavaWorld.

Odniesienia do metod: podkład

W moim poprzednim samouczku dotyczącym języka Java 101 wprowadziłem wyrażenia lambda, które są używane do definiowania anonimowych metod, które można następnie traktować jako wystąpienia funkcjonalnego interfejsu. Czasami wyrażenie lambda nie robi nic więcej niż wywołanie istniejącej metody. Na przykład, poniższy fragment kodu wykorzystuje lambda wywołania System.outjest void println(s)metody przy lambda jednym argument-- s's typu nie jest jeszcze znana:

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

Lambda przedstawia (s)swoją formalną listę parametrów i treść kodu, którego System.out.println(s)wyrażenie wypisuje swartość w standardowym strumieniu wyjściowym. Nie ma wyraźnego typu interfejsu. Zamiast tego kompilator wnioskuje z otaczającego kontekstu, który interfejs funkcjonalny ma zostać utworzony. Na przykład rozważmy następujący fragment kodu:

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

Kompilator analizuje poprzednią deklarację i ustala, że metoda java.util.function.Consumerpredefiniowanego interfejsu funkcjonalnego jest void accept(T t)zgodna z formalną listą parametrów lambdy ( (s)). Określa on również, że accept()„s voidmecze Zwraca typ println()” s voidtyp zwracany. Lambda jest więc związana z Consumer.

Dokładniej, lambda jest zobowiązana Consumer. Kompilator generuje kod, tak że wywołanie Consumer"S void accept(String s)wyników metoda w argumencie przekazanym ssą przekazywane System.outjest void println(String s)sposobem. To wywołanie jest pokazane poniżej:

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

Aby zapisać naciśnięcia klawiszy, można zastąpić lambdę odwołaniem do metody , które jest zwartym odwołaniem do istniejącej metody. Na przykład, następujące zastępuje fragment kodu (String s) -> System.out.println(s)z System.out::println, w którym ::oznacza to, że System.out„S void println(String s)sposób jest określany:

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

Nie jest konieczne określanie formalnej listy parametrów dla poprzedniego odwołania do metody, ponieważ kompilator może wywnioskować tę listę na podstawie rzeczywistego argumentu typu Consumertego sparametryzowanego typu java.lang.Stringzastępuje Tw void accept(T t), a także jest typem pojedynczego parametru w System.out.println()wywołaniu metody treści lambda .

Szczegółowe odniesienia do metod

Referencyjny Sposób jest składniowym skrót tworzenia lambda z istniejącego sposobu. Zamiast udostępniać treść implementacji, odwołanie do metody odwołuje się do metody istniejącej klasy lub obiektu. Podobnie jak w przypadku lambda odwołanie do metody wymaga typu docelowego.

Za pomocą odwołań do metod można odwoływać się do metod statycznych klasy, powiązanych i niezwiązanych metod niestatycznych oraz konstruktorów. Można również używać odwołań do metod, aby odwoływać się do metod instancji w nadklasie i bieżących typach klas. Przedstawię każdą z tych kategorii referencyjnych metod i pokażę, jak są one używane w małej demonstracji.

Dowiedz się więcej o odwołaniach do metod

Po przeczytaniu tej sekcji zapoznaj się z odwołaniami do metod w języku Java 8 (Toby Weston, luty 2014 r.), Aby uzyskać więcej informacji na temat odwołań do metod w kontekstach metod związanych i niezwiązanych niestatycznych.

Odniesienia do metod statycznych

Odniesienia metoda statyczna odnosi się do metody statycznej klasy określonej. Jego składnia to , gdzie identyfikuje klasę i identyfikuje metodę statyczną. Przykładem jest . Listing 1 przedstawia odwołanie do metody statycznej.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

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

main()Metoda z listy 1 sortuje parę tablic całkowitych za pomocą metody java.util.Arraysklasy static void sort(int[] a), która pojawia się w odwołaniu do metody statycznej i równoważnych kontekstach wyrażenia lambda. Po posortowaniu tablicy forpętla wypisuje zawartość posortowanej tablicy do standardowego strumienia wyjściowego.

Zanim będziemy mogli użyć odwołania do metody lub lambdy, musi być ona powiązana z interfejsem funkcjonalnym. Używam predefiniowanego Consumerinterfejsu funkcjonalnego, który spełnia wymagania metody / odniesienia lambda. W ROZPOCZYNA operacja sortowania przepuszczając tablicę być sortowana w Consumer„s accept()metody.

Skompiluj Listing 1 ( javac MRDemo.java) i uruchom aplikację ( java MRDemo). Zobaczysz następujące dane wyjściowe:

2 5 10 17 19 3 4 5 14 19 21

Odwołania do powiązanych metod niestatycznych

Związany niestatyczny Referencyjny Sposób odnosi się do nie-statyczne metody, która jest związana z odbiornika obiektu. Jego składnia to , gdzie identyfikuje odbiorcę i określa metodę instancji. Przykładem jest . Listing 2 przedstawia powiązane niestatyczne odwołanie do metody.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

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

main()Metoda z listy 2 przypisuje ciąg do Stringzmiennej, sa następnie wywołuje print()metodę klasy z funkcjonalnością, aby uzyskać długość tego ciągu jako argument tej metody. print()jest wywoływana w odwołaniu do metody ( s::length- length()jest powiązany z s), równoważnej lambdzie i równoważnych kontekstach klas anonimowych.

Zdefiniowałem print()użycie java.util.function.Supplierpredefiniowanego interfejsu funkcjonalnego, którego get()metoda zwraca dostawcę wyników. W tym przypadku Supplierinstancja przekazana do print()implementuje swoją get()metodę zwracania s.length(); print()wyprowadza tę długość.

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;powoduje, że kompilator poszukiwania konstruktora że trwa Stringargumentu, ponieważ Function„S apply()metoda wymaga pojedyncza (w tym kontekście) Stringargumentów. Wykonanie function.apply("some name")powoduje "some name"przekazanie do MRDemo(String name).