Започнете с препратки към методи в Java

Наред с ламбдите, Java SE 8 донесе препратки към метода към езика Java. Този урок предлага кратък преглед на препратките към методи в Java, след което започва да ги използвате с примери за Java код. В края на урока ще знаете как да използвате препратки към методи за препращане към статични методи на клас, обвързани и несвързани нестатични методи и конструктори, както и как да ги използвате за препращане към методи на екземпляр в суперклас и текущ клас видове. Ще разберете и защо много разработчици на Java са приели ламбда изрази и препратки към методи като по-чиста и по-проста алтернатива на анонимните класове.

Имайте предвид, че примерите за кодове в този урок са съвместими с JDK 12.

изтегляне Вземете кода Изтеглете изходния код, например приложения в този урок. Създадено от Jeff Friesen за JavaWorld.

Препратки към метода: Грунд

Предишният ми урок за Java 101 въведе ламбда изрази, които се използват за дефиниране на анонимни методи, които след това могат да бъдат третирани като екземпляри на функционален интерфейс. Понякога ламбда израз не прави нищо повече от извикване на съществуващ метод. Например, следния фрагмент от код използва ламбда да се позове System.outе void println(s)метод на ламбда сингъл argument-- sе вид все още не е известна:

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

Ламбда се представя (s)като официален списък с параметри и като кодово тяло, чийто System.out.println(s)израз отпечатва sстойността на стандартния изходен поток. Той няма изричен тип интерфейс. Вместо това, компилаторът извежда от околния контекст кой функционален интерфейс да създаде. Например, помислете за следния фрагмент от код:

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

Компилаторът анализира предишната декларация и определя, че методът на java.util.function.Consumerпредварително дефинирания функционален интерфейс void accept(T t)съответства на списъка с официални параметри на ламбда ( (s)). Той също така определя, че accept()voidот типа връщане мачове println()е voidтип замяна. По този начин ламбдата е свързана с Consumer.

По-конкретно, ламбдата е длъжна Consumer. Компилаторът генерира код, така че позоваването на Consumervoid accept(String s)метод води аргумент низ премина към sкоито да се прехвърлят към System.outе void println(String s)метод. Това извикване е показано по-долу:

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

За да запазите натисканията на клавиши, можете да замените ламбда с препратка към метод , която е компактна препратка към съществуващ метод. Например, следните код фрагмент замества (String s) -> System.out.println(s)с System.out::println, където ::означава, че System.outе void println(String s)метод е посочен:

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

Не е необходимо да се уточни официално списъка с параметри за предходния референтен метод, защото компилаторът да заключим този списък въз основа на Consumerтова се зададат параметри тип на java.lang.Stringдействителните тип аргумент заменя Tв void accept(T t), и също е от типа на единната параметър в организма ламбда System.out.println()метод повикване.

Позовавания на метода в дълбочина

А референтен метод е синтактичен пряк път за създаване на ламбда от съществуващ метод. Вместо да предостави тяло на изпълнение, препратката към метод се отнася до съществуващ клас или метод на обект. Както при ламбда, препратката към метод изисква целеви тип.

Можете да използвате препратки към методи за препращане към статични методи на клас, обвързани и несвързани нестатични методи и конструктори. Можете също така да използвате препратки към методи, за да се позовавате на методи на екземпляр в типове суперклас и текущ клас. Ще ви запозная с всяка от тези референтни категории методи и ще покажа как се използват в малка демонстрация.

Научете повече за препратките към методи

След като прочетете този раздел, разгледайте Референции на методи в Java 8 (Тоби Уестън, февруари 2014 г.) за повече представа за референциите на методи в обвързан и несвързан нестатичен контекст на метода.

Препратки към статични методи

А позоваване статичен метод се отнася до метод статично в определен клас. Синтаксисът му е , където идентифицира класа и идентифицира статичния метод. Пример за това е . Листинг 1 показва статичен референтен метод.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

Листинг 1. MRDemo.java (версия 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()Методът на Списък 1 сортира двойка целочислени масиви чрез метода на java.util.Arraysкласа static void sort(int[] a), който се появява в статичен справочен метод и еквивалентни контексти на ламбда изрази. След сортиране на масив, forцикъл отпечатва съдържанието на сортирания масив към стандартния изходен поток.

Преди да можем да използваме препратка към метод или ламбда, тя трябва да бъде обвързана с функционален интерфейс. Използвам предварително дефинирания Consumerфункционален интерфейс, който отговаря на изискванията за справка на метода / ламбда. Започване на работа сортиране по преминаване на масива да бъдат сортирани, за да Consumerе accept()метод.

Компилирайте списък 1 ( javac MRDemo.java) и стартирайте приложението ( java MRDemo). Ще наблюдавате следния изход:

2 5 10 17 19 3 4 5 14 19 21

Препратки към обвързани нестатични методи

А свързан не-статичен референтен метод се отнася до не-статичен метод, който е свързан към приемник обект. Синтаксисът му е , където идентифицира получателя и идентифицира метода на екземпляра. Пример за това е . Листинг 2 показва свързана нестатична препратка към метод.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

Листинг 2. MRDemo.java (версия 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()Методът на Listing 2 присвоява низ на Stringпроменлива sи след това извиква print()метода на класа с функционалност, за да получи дължината на този низ като аргумент на този метод. print()се извиква в препратка към метод ( s::length- length()е свързана с s), еквивалентен ламбда и еквивалентен контекст на анонимен клас.

Определих print()да използвам java.util.function.Supplierпредварително дефинирания функционален интерфейс, чийто get()метод връща доставчик на резултати. В този случай Supplierекземплярът, предаден на print()реализира своя get()метод за връщане s.length(); print()извежда тази дължина.

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;причинява компилаторът да търсят конструктор, който взема Stringаргумент, защото Functionе apply()метод изисква само една (в този контекст) Stringаргумент. Изпълнение на function.apply("some name")резултатите при "some name"предаване на MRDemo(String name).