Ссылки на методы (method reference) в Java - Вопросы
Всего: 17 вопросов
1. Что такое ссылки на методы (method reference) в Java?
Что такое ссылки на методы (method reference) в Java?
Лямбда-выражения используются для создания анонимных методов. Если лямбда-выражение не делает ничего, кроме вызова одного уже существующего метода, лучше сослаться на этот метод по имени — для этого и нужны ссылки на методы (method reference). Это сокращённая запись лямбда-выражения для метода, у которого уже есть имя.
Синтаксис использует оператор :: (двойное двоеточие): слева указывается класс или объект, справа — имя метода без скобок и без аргументов. Ссылки на методы появились в Java 8 и, как и лямбда-выражения, могут присваиваться только функциональному интерфейсу.
Consumer<String> consumer = str -> System.out.println(str); // лямбда
Consumer<String> consumer = System.out::println; // ссылка на метод
2. Какие бывают виды ссылок на методы в Java?
Какие бывают виды ссылок на методы в 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 = 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));
Перепишите пример, используя ссылку на метод:
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");
Перепишите пример, используя ссылку на метод:
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());Ссылка на нестатический метод конкретного объекта:
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 = 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"));
Перепишите пример, используя ссылку на метод. Почему здесь не стоит использовать 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[]. Это частная форма ссылки на конструктор, а не отдельный пятый вид ссылок.