1.4. FP, Lambda, Stream API Flashcards
Что такое lambda-выражение?
Лямбда-выражение - это краткая форма записи реализации абстрактного метода функционального интерфейса, которая может быть передана в качестве параметра.
- Синтаксис: (параметры) -> { тело }.
- В Java лямбда-выражение выполняет роль анонимной функции.
Что такое функциональные интерфейсы?
Функциональный интерфейс - это интерфейс, который содержат только один абстрактный метод, не считая методы класса Object.
- Обозначается аннотацией @FunctionalInterface.
- Примеры: Runnable, Comparator, Function, Supplier, Operator.
Перечислите функциональные интерфейсы из пакета java.util.function.
- Supplier - ничего не принимает, возвращает объект.
- Consumer (BiConsumer) - принимает объект и выполняет действие с ним, ничего не возвращает.
- Predicate (BiPredicate) - проверяет условие, возвращает boolean.
- Function (BiFunction) - преобразует входной параметр в значение другого типа.
- UnaryOperator (BinaryOperator) - разновидность Function, где входной и выходной типы совпадают.
Что такое функции высшего порядка?
Функции высшего порядка — это функции, которые принимают или возвращают другие функции.
Какие функциональные интерфейсы из пакета java.util.function поддерживают функции высшего порядка?
- Function, BiFunction: методы andThen(), compose().
- Predicate, BiPredicate: методы and(), or(), negate().
- Consumer, BiConsumer: метод andThen().
compose() - сначала выполнить указанную функцию, а затем применить текущую. Последовательность происходит в обратном порядке, чем при использовании andThen.
negate() - меняет результат текущего предиката на противоположный (true → false, false → true).
Что такое ссылки на методы?
Ссылки на методы (method references) — это сокращенный способ записи существующих методов в лямбда-выражении.
Типы ссылок:
- Статический метод: СlassName::method
- Метод объекта: instance::method
- Конструктор: ClassName::new
Сигнатура ссылки на метод/конструктор должна совпадать с сигнатурой абстрактного метода функционального интерфейса.
Что такое ссылки на конструкторы?
Ссылки на конструкторы (ClassName::new) используются для создания объектов.
- Без параметров: реализует Supplier<T>.</T>
- С параметрами: реализует Function<T, R> или BiFunction<T, U, R>.
Расскажите о зоне видимости переменных в lambda – выражениях?
- Поля класса и статические переменные: доступны для чтения и изменения.
- Локальные переменные: доступны только для чтения, должны быть final или effectively final.
- Переменные внутри лямбда-выражения: доступны только внутри.
Как быть в ситуации, если внутри lambda - выражения операторы могут выкинуть исключение?
- Преобразовать исключение в RuntimeException в try-catch.
- Использовать функциональный интерфейс, который декларирует checked exceptions в throws
т.к. в Java нельзя выбросить checked исключение из метода, если оно не указано в throws.
Что такое Stream API?
Stream API - это инструмент для обработки данных в функциональном стиле через цепочку операций. Позволяет: фильтровать, сортировать, преобразовывать, агрегировать и параллельно обрабатывать данные.
Особенности: потоки не изменяют исходные данные.
Расскажите, какие шаблоны проектирования используются внутри Stream API? (Builder, Strategy, Decorator, Factory Method, Pipeline).
- Builder - позволяет пошагово создавать сложные объекты, давая пользователю возможность комбинировать компоненты.
- Strategy - обеспечивает выбор алгоритма выполнения во время работы программы.
- Decorator - добавляет новую функциональность объекту, не изменяя его структуру.
- Factory Method - создание потоков (Stream.of()) с использованием подклассов вместо прямого создания экземпляров.
- Pipeline - конвейерная обработка данных через комбинацию промежуточных и терминальных операций.
Объясните, где они используются в Stream API.
- Builder - создает цепочки операций (map(), filter(), sorted()) для обработки данных поэтапно.
- Strategy - позволяет выбирать алгоритм выполнения (stream() или parallelStream()).
- Decorator - методы filter(), map(), flatMap() и другие конвейерные операции действуют как декораторы, добавляя функциональность к потоку данных без изменения исходного источника данных.
- Factory Method - создает потоки через методы Stream.of() и Arrays.stream().
- Pipeline - представляет собой последовательность конвейерных операций, где каждая операция обрабатывает поток данных и передает результат следующей операции в цепочке.
Что такое конвейерные и терминальные операции?
- Конвейерные операции - это промежуточные операции, которые выполняют преобразование данных и возвращают новый поток. Эти операции выполняются только после вызова терминальной операции.
- Терминальные операции - инициируют выполнение всех промежуточных операций, возвращают результат и закрывают поток.
Перечислите конвейерные (промежуточные) методы Stream API.
- filter() - фильтрует элементы потока по предикату.
- map() - преобразует каждый элемент потока, применяя функцию.
- flatMap() - объединяет вложенные потоки в один.
- distinct() - удаляет дубликаты из потока.
- sorted() - сортирует элементы потока по компаратору, по умолчанию (natural order).
- peek() - выполняет действие для каждого элемента потока без изменения самого потока. Используется для отладки.
- limit() - ограничивает количество элементов в потоке до заданного количества.
- skip() - пропускает первые n элементов потока.
Дополнительные конвейерные методы для примитивных потоков:
- mapToInt(), mapToLong(), mapToDouble() - преобразуют элементы потока в поток примитивов int, long, double.
- flatMapToInt(), flatMapToLong(), flatMapToDouble() - преобразует элементы в поток примитивов int, long, double, затем объединяет все потоки в один.
Перечислите терминальные методы Stream API.
- forEach() - выполняет действие для каждого элемента потока.
- collect() - собирает элементы потока в коллекцию, строку или другой контейнер.
- reduce() - выполняет агрегацию элементов, сводя их к одному результату.
- count() - возвращает количество элементов в потоке.
- toArray() - преобразует поток в массив.
- min/max() - возвращает минимальный/максимальный элемент потока согласно компаратору.
- findFirst() - возвращает первый элемент потока или Optional.empty.
- findAny() - возвращает любой элемент потока.
- allMatch() - проверяет, что все элементы соответствуют предикату.
- noneMatch() - проверяет, что ни один элемент не соответствует предикату.
- anyMatch() - проверяет, что хотя бы один элемент соответствует предикату.
Что такое отложенный выполнение lamdba?
Промежуточные операции потока выполняются только при вызове терминальной операции.
Что делает метод filter()?
filter() - фильтрует элементы по предикату.
Промежуточная операция, возвращает новый поток с элементами, прошедшими фильтр.
Что делает метод map()?
map() - преобразует каждый элемент потока, применяя функцию и возвращает новый поток с результатами преобразования.
Что делает метод flatMap()?
flatMap() - объединяет вложенные потоки в один.
Что делает метод collect?
collect() - собирает элементы в коллекцию, массив и другую структуру данных.
Что делает метод findFirst?
findFirst() - возвращает первый элемент потока в виде Optional.
Что делает метод reduce?
reduce() - агрегирует элементы потока в одно значение. Ассоциативную бинарную операцию.
Применяться для суммирования, нахождения макс, произведения чисел, конкатенации строк и других операций, которые объединяют элементы потока в одно значение.
Что делают методы min и max?
min()/max() - возвращают минимальный/максимальный элемент потока по компаратору. Терминальная операция, возвращает Optional<T>
.
Что делают методы count, sum, average?
- count() - возвращает количество элементов потока.
-
sum() - возвращает сумму всех элементов потока примитивных типов IntStream, LongStream или DoubleStream. Для потоков объектов, таких как Stream
<Integer>
, необходимо использовать mapToInt() и затем применить sum(). - average() - вычисляет среднее значение элементов в потоке примитивных типов IntStream, LongStream или DoubleStream. Возвращает OptionalDouble.
Что делают методы forEach и peek?
- forEach() - выполняет действие для каждого элемента потока. Терминальная операция.
- peek() - выполняет промежуточное действие, не изменяя поток. Используется для отладки, логирования.
Что делают методы skip и limit?
- skip() - пропускает первые n элементов в начале потока.
- limit() - оставляет первые n элементов. Возвращает новый поток, содержащий только первые n элементов исходного потока.
Что делают методы allMatch(), noneMatch() и anyMatch()?
- allMatch() - проверяет, что все элементы соответствуют предикату.
- noneMatch() - проверяет, что ни один элемент не соответствует предикату.
- anyMatch() - проверяет, что хотя бы один элемент предикату.
Все три метода терминальные, возвращающие boolean.
Что делают методы mapToInt(), flatMapToInt(), mapToObj()?
- mapToInt() - преобразует элементы в int и возвращает IntStream.
- flatMapToInt() - преобразует каждый элемент потока в IntStream и объединяет все результирующие потоки в один IntStream.
-
mapToObj() - преобразует примитивный поток IntStream, LongStream, DoubleStream в любой объектный Stream
<U>
, используя функцию преобразования.
Что такое числовой поток?
Числовой поток IntStream, LongStream, DoubleStream - это поток для работы с примитивными типами данных int, long и double.
Преимущества:
- Не нужен autoboxing/unboxing примитивных типов.
- Предоставляют методы sum(), average(), min(), max() и другие.
- Меньше расход памяти.
Чем отличается Stream<Integer>
от IntStream<int>
?
-
Stream
<Integer>
- поток объектов типа Integer, требуется autoboxing/unboxing, менее эффективен для чисел. - IntStream - поток примитивов типа int, не нужен autoboxing/unboxing, более производителен для числовых операций.
Что делает метод boxed?
boxed() - преобразует примитивный поток в соответствующий ему объектный.
-
IntStream → Stream
<Integer>
-
LongStream → Stream
<Long>
-
DoubleStream → Stream
<Double>
Возможно ли прервать выполнение потока по аналогии с break?
Да, в следующих случаях:1.
Раннее завершение:
- anyMatch(), allMatch(), noneMatch() - останавливают поток при достижении результата.
- findFirst(), findAny() - завершают поток после нахождения элемента.
2.
Ограничение:
- limit() - ограничивает число обрабатываемых элементов.
- takeWhile() - завершает поток при первом несоответствии предикату.
3.
Исключение: поток
прерывается вручную через выброс исключения.
Возможно ли пропустить элемент потока по аналогии с continue?
В Stream API нет прямого аналога continue, но пропуск элементов возможен:
- filter() - фильтрует элементы по предикату.
- skip() - пропускает первые n элементов.
- takeWhile() - завершает поток при первом несоответствии предикату.
Что такое Optional?
- Optional - это класс-обёртка для объекта, который может содержать значение или быть пустым.
- Помогает избежать NullPointerException и предоставляет методы для обработки значений, которые могут быть пустыми.
Перечислите методы Optional.
- of() - создаёт Optional с непустым значением.
- ofNullable() - создаёт Optional, допускающий null.
- empty() - создаёт пустой Optional.
- isPresent() - проверяет наличие значения.
- isEmpty() - проверяет отсутствие значения.
- get() - возвращает значение, если оно есть.
- orElse() - возвращает значение или дефолтное.
- orElseGet() - возвращает значение или результат Supplier.
- orElseThrow() - выбрасывает исключение, если значения нет.
- ifPresent() - выполняет действие, если значение есть.
- ifPresentOrElse() - выполняет действие или альтернативно.
- map() - преобразует значение применяя функцию.
-
flatMap() - преобразует значение в другой Optional, убирая вложенность
Optional<Optional<T>>
. - filter() - фильтрует элементы по предикату.
В чем разница между методами orElse() и orElseGet()?
- orElse() - всегда вычисляет и возвращает заданное значение, даже если оно не понадобится.
- orElse() - вычисляет значение только при его необходимости, используя Supplier.
Расскажите про фабричные методы List.of, Set.of, Map.of?
Эти методы создают неизменяемые коллекции и не поддерживают null:
- List.of() - создаёт неизменяемый список.
- Set.of() - создаёт неизменяемое множество.
- Map.of() - создаёт неизменяемую карту (до 10 пар ключ-значение). Для карт с более чем 10 парами существует перегрузка Map.ofEntries().
Если неизменяемая коллекция содержит null, выбрасывается NullPointerException. Попытка изменения неизменяемую коллекцию выбросит UnsupportedOperationException.
Для чего используется var?
- var используется для упрощения кода, позволяя компилятору определять тип переменной по типу её значения.
- var не является ключевым словом и зарезервировано как идентификатор, поэтому нельзя использовать как название переменной, метода итд.
В каких случаях можно использовать var?
var можно использовать:
- для локальных переменных.
- в циклах for.
- с лямбда-выражениями.
Нельзя использовать для полей класса, параметров метода и возвращаемых значений или если тип не может быть выведен однозначно.