Функциональная Java
Лямбды, функциональные интерфейсы, ссылки на методы, основы обобщений и Optional.
7 вопросов
JuniorТеорияОчень частоЧто такое функциональный интерфейс и почему он позволяет использовать лямбды?
Что такое функциональный интерфейс и почему он позволяет использовать лямбды?
Функциональный интерфейс объявляет ровно один абстрактный метод, опционально помеченный @FunctionalInterface, чтобы компилятор это проверял. Поскольку единственный метод однозначен, интерфейс может быть целевым типом лямбды или ссылки на метод, которые задают его реализацию. Примеры — Runnable, Function, Predicate.
Типичные ошибки
- ✗Думать, что default- и статические методы нарушают правило одного абстрактного метода и лишают интерфейс статуса функционального
- ✗Считать
@FunctionalInterfaceобязательной, а не опциональной проверкой времени компиляции - ✗Полагать, что только лямбда, а не ссылка на метод, может быть целью функционального интерфейса
Уточняющие вопросы
- →Как default-методы позволяют интерфейсу оставаться функциональным, добавляя поведение?
- →Почему компилятор отклоняет лямбду для интерфейса с двумя абстрактными методами?
JuniorТеорияОчень частоЧто такое лямбда-выражение в Java и что оно может захватывать?
Что такое лямбда-выражение в Java и что оно может захватывать?
Лямбда вроде (x) -> x * 2 — это краткая inline-реализация единственного абстрактного метода функционального интерфейса: параметры слева, тело справа. Как замыкание она может захватывать локальные переменные из окружающей области, но только effectively-final — те, что не переприсваиваются после инициализации. Она также видит поля окружения и this.
Типичные ошибки
- ✗Пытаться захватить и переприсвоить локальную переменную, нарушая правило effectively-final
- ✗Ожидать, что
thisлямбды означает саму лямбду, тогда как он ссылается на окружающий экземпляр - ✗Путать лямбду с анонимным классом, у которого своя отдельная область и свой
this
Уточняющие вопросы
- →Почему Java требует, чтобы захватываемые локальные переменные были effectively final?
- →Как ссылка на метод соотносится с эквивалентным лямбда-выражением?
JuniorТеорияЧастоЧто такое обобщения в Java и какую проблему они решают?
Что такое обобщения в Java и какую проблему они решают?
Обобщения параметризуют тип или метод аргументом типа вроде <T>, поэтому одно определение работает для многих конкретных типов. Они дают типобезопасность на этапе компиляции — компилятор отвергает неверный тип элемента и сам вставляет приведения, так что вручную их писать не нужно. Граница <T extends Number> ограничивает аргумент типом и его подтипами.
Типичные ошибки
- ✗Ожидать, что аргумент обобщённого типа существует в рантайме, игнорируя его удаление при стирании
- ✗Считать, что обобщения позволяют смешивать типы элементов, а не закрепляют один на этапе компиляции
- ✗Пытаться использовать примитив вроде
intкак аргумент типа вместо класса-обёртки
Уточняющие вопросы
- →Что разрешает wildcard с верхней границей
<? extends T>против обычного<T>? - →Почему аргумент типа должен быть ссылочным типом, а не примитивом?
MiddleТеорияЧастоЧто делает каждый из Function, Supplier, Consumer и Predicate?
Что делает каждый из Function, Supplier, Consumer и Predicate?
Это четыре базовые формы из java.util.function, различаемые по тому, что они принимают и что возвращают. Function<T,R> принимает один аргумент и возвращает результат (apply). Supplier<T> не принимает ничего и порождает значение (get). Consumer<T> принимает значение и ничего не возвращает, действуя только побочным эффектом (accept). Predicate<T> принимает значение и возвращает boolean (test). Двухаргументные и примитивные варианты вроде BiFunction и IntPredicate следуют той же схеме.
Типичные ошибки
- ✗Путать
SupplierиConsumer, которые соответственно не принимают ничего и принимают значение - ✗Ожидать, что
Predicateвернёт произвольный результат, а неboolean - ✗Не знать о примитивных вариантах вроде
IntPredicate, которые избегают упаковки
Уточняющие вопросы
- →Почему
Consumerничего не возвращает и как соединить два таких в цепочку? - →Зачем нужен
IntPredicate, если естьPredicate<Integer>?
MiddleТеорияЧастоКакие четыре формы имеет ссылка на метод в Java?
Какие четыре формы имеет ссылка на метод в Java?
Их четыре: static-метод (Integer::parseInt), метод конкретного объекта (out::println), метод произвольного объекта типа (String::length, где получатель приходит первым аргументом) и конструктор (ArrayList::new). Каждая — сокращение для лямбды, тело которой не делает ничего, кроме вызова этого метода, и она нацеливается на функциональный интерфейс ровно так же, как это сделала бы лямбда.
Типичные ошибки
- ✗Не знать, что на конструктор вообще можно сослаться через
Type::new - ✗Путать связанную форму
out::printlnс несвязаннойString::length, у которой получатель — первый аргумент - ✗Думать, что ссылка на метод идёт через рефлексию, а не компилируется в ту же цель, что и лямбда
Уточняющие вопросы
- →Почему ссылка на метод не заменяет лямбду, тело которой делает больше одного вызова?
- →Как компилятор отличает связанную форму от несвязанной формы метода экземпляра?
MiddleТеорияЧастоЧто такое контейнер Optional и как им следует пользоваться?
Что такое контейнер Optional и как им следует пользоваться?
Optional<T> — контейнер, который либо хранит значение, либо пуст, моделируя результат, который может отсутствовать. Он призван заменить возврат null, делая отсутствие явным в типе и уводя вызывающих от NullPointerException. Доступ — через map, orElse или ifPresent, а не слепой get, чтобы пустой случай обрабатывался осознанно.
Типичные ошибки
- ✗Вызывать
getбез проверки наличия, что бросаетNoSuchElementException, когда пусто - ✗Присваивать
nullпеременнойOptionalвместо использованияOptional.empty - ✗Применять
Optionalдля полей и параметров, а не только для возвращаемых типов, как задумано
Уточняющие вопросы
- →Чем
flatMapотличается отmap, когда функция сама возвращаетOptional? - →Почему использование
Optionalдля поля или параметра метода не приветствуется?
SeniorТеорияИногдаКакие антипаттерны чаще всего встречаются в коде, использующем Optional?
Какие антипаттерны чаще всего встречаются в коде, использующем Optional?
Вызов get() без проверки наличия, который лишь сдвигает NullPointerException на строку ниже. Использование Optional для полей, параметров или элементов коллекций, хотя он задуман только для возвращаемых типов. Присваивание null переменной Optional вместо Optional.empty(). Связка isPresent() и get() — переодетая проверка на null, лучше map, filter или ifPresent. И orElse для дорогого запасного значения, аргумент которого вычисляется даже при наличии значения.
Типичные ошибки
- ✗Считать
get()безопасным аксессором, а не непроверенной распаковкой, которая бросает исключение на пустом значении - ✗Ставить
Optionalна поля и параметры методов вместо того, чтобы ограничить его возвращаемыми типами - ✗Полагать, что
orElseленив, какorElseGet, и передавать ему дорогое выражение
Уточняющие вопросы
- →Почему
OptionalнеSerializableи что это означает для поля сущности? - →Когда именно
orElseвычисляет свой аргумент, хотя значение присутствует?