Ссылки на методы (method reference) в Java - Вопросы

Всего: 17 вопросов

1. 

Что такое ссылки на методы (method reference) в Java?

Лямбда-выражения используются для создания анонимных методов. Если лямбда-выражение не делает ничего, кроме вызова одного уже существующего метода, лучше сослаться на этот метод по имени — для этого и нужны ссылки на методы (method reference). Это сокращённая запись лямбда-выражения для метода, у которого уже есть имя.

Синтаксис использует оператор :: (двойное двоеточие): слева указывается класс или объект, справа — имя метода без скобок и без аргументов. Ссылки на методы появились в Java 8 и, как и лямбда-выражения, могут присваиваться только функциональному интерфейсу.

Consumer<String> consumer = str -> System.out.println(str); // лямбда
Consumer<String> consumer = System.out::println;            // ссылка на метод
2. 

Какие бывают виды ссылок на методы в Java?

Ссылки на методы бывают четырёх видов:

  • ссылка на статический метод — ContainingClass::staticMethodName, например Integer::parseInt;
  • ссылка на нестатический метод конкретного объекта (bound receiver) — containingObject::instanceMethodName, например System.out::println;
  • ссылка на нестатический метод любого объекта конкретного типа (unbound receiver) — ContainingType::methodName, например String::toLowerCase;
  • ссылка на конструктор — ClassName::new, например ArrayList::new.

Ссылка на конструктор массива Type[]::new — это частная форма ссылки на конструктор, а не отдельный пятый вид.

3. 

Каков синтаксис ссылки на статический метод?

ContainingClass::staticMethodName

Все параметры лямбда-выражения один в один становятся аргументами статического метода:

Function<String, Integer> parse = Integer::parseInt;   // s -> Integer.parseInt(s)
BiFunction<Integer, Integer, Integer> max = Math::max;  // (a, b) -> Math.max(a, b)
4. 

Каков синтаксис ссылки на нестатический метод конкретного объекта?

containingObject::instanceMethodName

Слева от :: стоит переменная или выражение, а не имя класса. Получатель зафиксирован в самой ссылке (bound receiver), все параметры функционального интерфейса уходят в аргументы метода:

Consumer<String> consumer = System.out::println;  // x -> System.out.println(x)

Получателем может быть и текущий объект: допустимы записи this::print и super::print.

5. 

Каков синтаксис ссылки на нестатический метод любого объекта конкретного типа?

ContainingType::methodName

Слева стоит имя класса, хотя метод нестатический (unbound receiver). Первый параметр функционального интерфейса становится объектом, на котором вызывается метод, а остальные параметры — его аргументами:

Function<String, String> f = String::toLowerCase;             // s -> s.toLowerCase()
BiFunction<String, String, Boolean> eq = String::equalsIgnoreCase; // (a, b) -> a.equalsIgnoreCase(b)
6. 

Каков синтаксис ссылки на конструктор?

ClassName::new

ClassName не может быть абстрактным классом или интерфейсом — объект такого типа создать нельзя.

Supplier<List<String>> listSupplier = ArrayList::new;  // () -> new ArrayList<>()
Function<String, Person> creator = Person::new;         // name -> new Person(name)
7. 

Перепишите пример, используя ссылку на метод:

Function<String, Boolean> function = e -> Boolean.valueOf(e);
System.out.println(function.apply("TRUE"));

Это ссылка на статический метод:

Function<String, Boolean> function = Boolean::valueOf;
System.out.println(function.apply("TRUE")); // true
8. 

Перепишите пример, используя ссылку на метод:

IntFunction<String> function = i -> String.valueOf(i);
System.out.println(function.apply(450));

Ссылка на статический метод; нужную перегрузку valueOf(int) компилятор выбирает по целевому типу IntFunction<String>:

IntFunction<String> function = String::valueOf;
System.out.println(function.apply(450)); // 450
9. 

Когда используется ссылка на нестатический метод конкретного объекта?

Этот вид ссылок используется, когда лямбда-выражение вызывает метод внешнего, уже существующего объекта. Объект-получатель зафиксирован в самой ссылке, поэтому все параметры функционального интерфейса становятся аргументами метода.

Integer integer = 5;
Supplier<String> supplier = integer::toString;  // () -> integer.toString()

Отличить этот вид от ссылки на метод любого объекта типа просто: слева от :: стоит переменная или выражение, а не имя класса.

10. 

Перепишите пример, используя ссылку на метод:

Consumer<String> consumer = e -> System.out.println(e);
consumer.accept("OCPJP 8");

Это ссылка на нестатический метод конкретного объекта — объекта System.out:

Consumer<String> consumer = System.out::println;
consumer.accept("OCPJP 8"); // OCPJP 8
11. 

Перепишите пример, используя ссылку на метод:

Integer integer = 5;
Supplier<String> supplier = () -> integer.toString();
System.out.println(supplier.get());

Ссылка на нестатический метод конкретного объекта:

Integer integer = 5;
Supplier<String> supplier = integer::toString;
System.out.println(supplier.get()); // 5
12. 

Перепишите пример, используя ссылку на метод:

Function<String, String> function = s -> s.toLowerCase();
System.out.println(function.apply("OCPJP 8"));

Это ссылка на нестатический метод любого объекта конкретного типа: первый параметр интерфейса становится объектом, на котором вызывается метод.

Function<String, String> function = String::toLowerCase;
System.out.println(function.apply("OCPJP 8")); // ocpjp 8
13. 

Перепишите пример, используя ссылку на метод. Почему здесь не стоит использовать Integer::new?

Function<String, Integer> function = d -> Integer.valueOf(d);
System.out.println(function.apply("4"));

Заменяем лямбда-выражение ссылкой на статический метод:

Function<String, Integer> function = Integer::valueOf;
System.out.println(function.apply("4")); // 4

В старых примерах на этом месте часто встречается ссылка на конструктор Function<String, Integer> f = Integer::new;. Формально она компилируется, но конструкторы классов-обёрток объявлены устаревшими начиная с Java 9, а с Java 16 помечены как deprecated for removal: они всегда создают новый объект в обход кэша. Используйте фабричные методы — Integer::valueOf или Integer::parseInt (последний возвращает примитив int).

14. 

Если в классе объявлено несколько конструкторов, как компилятор выбирает нужный для ссылки на конструктор?

Компилятор сверяет сигнатуру абстрактного метода целевого функционального интерфейса со всеми конструкторами класса и выбирает наиболее подходящий по числу и типам параметров.

class SomeClass {
    SomeClass()         { System.out.println(0); }
    SomeClass(String s) { System.out.println(1); }
}

Supplier<SomeClass> s = SomeClass::new;          // конструктор без параметров
Function<String, SomeClass> f = SomeClass::new;  // конструктор с параметром String

Если ни один конструктор не подходит либо подходят сразу несколько и выбрать однозначно нельзя, будет ошибка компиляции.

15. 

Когда вычисляется выражение слева от оператора ::?

Один раз — в момент создания ссылки на метод, а не при каждом вызове. Поэтому такой код падает уже на второй строке:

String s = null;
Supplier<Integer> supplier = s::length; // NullPointerException прямо здесь
System.out.println(supplier.get());      // до этой строки дело не дойдёт

У эквивалентного лямбда-выражения () -> s.length() поведение другое: получатель читается при каждом вызове, и исключение возникнет только внутри supplier.get(). По этой же причине System.out::println навсегда запоминает поток вывода, который был установлен на момент создания ссылки, и последующий System.setOut(...) на неё не повлияет.

16. 

В каких случаях лямбда-выражение нельзя заменить ссылкой на метод?

Ссылка на метод заменяет только такое лямбда-выражение, которое не делает ничего, кроме одного вызова метода или конструктора и просто пробрасывает в него свои параметры в том же порядке. Заменить нельзя, если:

  • в теле лямбды несколько операций, условие или арифметика;
  • нужно поменять порядок аргументов: (a, b) -> f(b, a);
  • нужно подставить собственный аргумент или константу: s -> s.substring(1);
  • вызов неоднозначен и компилятор не может выбрать метод (ошибка reference to ... is ambiguous).
Function<String, String> ok   = String::trim;            // корректно
Function<String, String> fail = String::substring;       // не скомпилируется
Function<String, String> okLambda = s -> s.substring(1); // так и оставляем

Практическое правило: пишите ссылку на метод, если лямбда выглядит как x -> что-то(x) или x -> x.что-то().

17. 

Как создать массив с помощью ссылки на конструктор?

Используется форма Type[]::new, эквивалентная лямбда-выражению size -> new Type[size]:

IntFunction<String[]> arrayCreator = String[]::new;
String[] empty = arrayCreator.apply(3);

String[] result = Stream.of("a", "b", "c")
                        .toArray(String[]::new);
System.out.println(result.length); // 3

Чаще всего эта форма встречается в Stream.toArray(): с аргументом String[]::new метод вернёт String[], а без аргумента — Object[]. Это частная форма ссылки на конструктор, а не отдельный пятый вид ссылок.

Страница 1 из 1