сеньорчикОткрыть в Telegram
← все вопросывопросы для собеседований · Stream API

Вопросы по Stream API на собеседовании Java

Stream API спрашивают почти всегда, и главный вопрос звучит одинаково: почему цепочка ничего не сделала без терминальной операции. Дальше проверяют коллекторы и понимание, что параллельный стрим не бесплатный.

80 вопросов в банке·5 подтем·ниже разбор 9

Из чего состоит тема

Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.

Разборы подтем

Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.

Примеры вопросов с разбором

  1. #collectors_grouping1 / 9
    Что делает collect(Collectors.toList())?
    A)Собирает элементы стрима в List
    B)Складывает элементы в неизменяемый массив фиксированного размера, равного числу элементов стрима
    C)Подсчитывает количество элементов в стриме и возвращает его в виде целого числа типа long
    D)Соединяет все элементы в одну строку через запятую, вызывая у каждого метод toString
    показать ответ и разбор
    +A)Собирает элементы стрима в List

    // разбор: collect — универсальная терминальная операция сведения (reduction) в контейнер, а Collectors.toList() — готовый коллектор, материализующий элементы в List. Есть много готовых: toSet, toMap, joining, counting, groupingBy, partitioningBy, summingInt и др. (Отдельно Stream.toList() с Java 16 сразу даёт неизменяемый список.) collect работает и с параллельными стримами (у коллектора есть supplier/accumulator/combiner). Это основной способ «выгрузить» результат стрима в структуру данных.

  2. #lambdas_functional2 / 9
    Что такое функциональный интерфейс?
    A)Интерфейс ровно с одним абстрактным методом (SAM)
    B)Интерфейс, все методы которого объявлены как default и имеют готовую реализацию по умолчанию
    C)Интерфейс, помеченный аннотацией @FunctionalInterface, даже если абстрактных методов в нём несколько
    D)Специальный класс из пакета java.util.function, экземпляр которого не получится создать напрямую через new
    показать ответ и разбор
    +A)Интерфейс ровно с одним абстрактным методом (SAM)

    // разбор: Функциональный интерфейс — интерфейс с РОВНО ОДНИМ абстрактным методом (Single Abstract Method). Именно под него компилятор подставляет лямбду или ссылку на метод. default- и static-методы, а также методы Object (equals/hashCode) не считаются — их может быть сколько угодно. Аннотация @FunctionalInterface необязательна, но заставляет компилятор проверить это условие. Примеры: Runnable, Comparator, Predicate, Function.

  3. #optional_null3 / 9
    Зачем нужен Optional<T>?
    A)Явно выразить возможное отсутствие значения вместо возврата null
    B)Чтобы избавить программу от null: после введения Optional NullPointerException больше не возникает
    C)Чтобы автоматически кэшировать результат метода и не вычислять его повторно при следующих вызовах
    D)Чтобы сделать поле потокобезопасным — Optional синхронизирует доступ к обёрнутому значению
    показать ответ и разбор
    +A)Явно выразить возможное отсутствие значения вместо возврата null

    // разбор: Optional<T> — контейнер, который либо содержит значение, либо пуст. Его цель — сделать ОТСУТСТВИЕ значения явным в сигнатуре метода (Optional<User> findUser(...)), чтобы вызывающий не забыл обработать «не найдено», как это бывает с молчаливым null. Он поощряет безопасные операции (map/filter/orElse) вместо ручных проверок на null. Но это не «серебряная пуля»: сам Optional может быть использован неправильно (Optional.get без проверки, Optional-поля), и NPE так не исчезает магически.

  4. #stream_operations4 / 9
    Выполнится ли цепочка stream.filter(...).map(...) без терминальной операции?
    A)Нет — промежуточные операции ленивы, без терминальной ничего не запустится
    B)Да, filter и map выполняются немедленно в момент их вызова, независимо от наличия терминальной операции
    C)Да, но только filter; map откладывается до вызова терминальной операции сбора результата
    D)Нет, потому что такая цепочка не скомпилируется без завершающего вызова collect в конце
    показать ответ и разбор
    +A)Нет — промежуточные операции ленивы, без терминальной ничего не запустится

    // разбор: Промежуточные операции (filter, map, sorted, distinct, limit) ЛЕНИВЫ: они лишь строят конвейер и возвращают новый Stream, ничего не вычисляя. Реальная обработка запускается ТЕРМИНАЛЬНОЙ операцией (collect, forEach, reduce, count, findFirst и т.п.), которая «протягивает» элементы через весь конвейер — обычно за один проход и поэлементно. Без терминальной операции стрим бесполезен: лямбды в map/filter даже не вызовутся. Это и позволяет оптимизации (слияние операций, короткое замыкание).

  5. #streams_advanced5 / 9
    Что делает flatMap в стриме?
    A)Разворачивает каждый элемент в стрим и склеивает их в один плоский стрим
    B)Фильтрует стрим, оставляя только элементы, которые сами являются коллекциями или массивами
    C)Сортирует вложенные коллекции по возрастанию и объединяет их в один отсортированный стрим
    D)Преобразует стрим в плоскую строку, соединяя строковые представления всех элементов подряд
    показать ответ и разбор
    +A)Разворачивает каждый элемент в стрим и склеивает их в один плоский стрим

    // разбор: flatMap(f) применяет к каждому элементу функцию, возвращающую СТРИМ, и «сплющивает» все получившиеся под-стримы в один. Это операция «один-ко-многим»: List<List<Order>> → все Order одним стримом (flatMap(List::stream)); строки → все слова (flatMap(s -> Arrays.stream(s.split(" ")))). map даёт Stream<Stream<...>> — неудобную вложенность, а flatMap её убирает. Есть и примитивные flatMapToInt и др. Ключевой инструмент для работы с вложенными структурами.

  6. #collectors_grouping6 / 9
    Что вернёт Collectors.groupingBy(Employee::getDept)?
    A)List<Dept> — просто список всех встретившихся отделов без сотрудников, по одному разу каждый
    B)Map<Dept, List<Employee>> — элементы, сгруппированные по ключу
    C)Map<Employee, Dept> — отображение каждого сотрудника на его отдел в виде пар ключ-значение
    D)Set<Employee> — множество сотрудников с удалёнными дубликатами по признаку одинакового отдела
    показать ответ и разбор
    +B)Map<Dept, List<Employee>> — элементы, сгруппированные по ключу

    // разбор: groupingBy(classifier) собирает стрим в Map, где КЛЮЧ — результат классификатора (отдел), а ЗНАЧЕНИЕ по умолчанию — List элементов этой группы. Это SQL-подобный GROUP BY. Мощь — во втором аргументе (downstream-коллектор): groupingBy(dept, counting()) даёт число на отдел, groupingBy(dept, mapping(Employee::getName, toList())) — имена, groupingBy(dept, averagingInt(...)) — среднее. Так одной строкой считают агрегаты по группам.

  7. #lambdas_functional7 / 9
    Что означает, что лямбда захватывает «effectively final» переменную?
    A)Лямбда может менять захваченную локальную переменную, и её новое значение видно снаружи метода
    B)Локальную переменную можно использовать в лямбде, только если её не переприсваивают
    C)Лямбда захватывает копию переменной, поэтому переменную обязательно нужно объявлять с ключевым словом final
    D)Захватить можно и изменяемую локальную переменную, а effectively final относится только к полям класса
    показать ответ и разбор
    +B)Локальную переменную можно использовать в лямбде, только если её не переприсваивают

    // разбор: Лямбда (и анонимный класс) может использовать локальную переменную окружающего метода, только если та ФАКТИЧЕСКИ финальна: после инициализации ей не присваивают новое значение. Явно писать final не нужно — достаточно не переприсваивать. Причина: лямбда может пережить метод, а локальная переменная живёт в стеке — захватывается её значение (снимок), и разрешать изменения было бы неоднозначно. Для «изменяемого» состояния используют поле объекта или обёртку (массив/AtomicInteger).

  8. #optional_null8 / 9
    Чем orElse(x) отличается от orElseGet(supplier)?
    A)OrElse ленив и вызывает выражение лишь при пустом Optional, а orElseGet вычисляет значение заранее
    B)orElse вычисляет x всегда (даже если значение есть); orElseGet — лениво, только если пусто
    C)Они полностью идентичны; orElseGet добавили лишь для более короткой записи того же самого поведения
    D)OrElse возвращает Optional, а orElseGet — уже развёрнутое значение внутри обёртки без распаковки
    показать ответ и разбор
    +B)orElse вычисляет x всегда (даже если значение есть); orElseGet — лениво, только если пусто

    // разбор: Тонкое, но важное отличие. orElse(x): аргумент x — ОБЫЧНОЕ выражение, вычисляемое ВСЕГДА, ещё до проверки, даже если Optional непустой. Если x — дорогой вызов (новый объект, обращение к БД), это лишняя работа/побочный эффект. orElseGet(supplier): supplier вызывается ЛЕНИВО — только когда Optional пуст. Правило: константа/дешёвое значение — orElse; дорогое/с побочным эффектом — orElseGet. Частая ошибка производительности — orElse(expensiveCall()).

  9. #stream_operations9 / 9
    Можно ли повторно использовать один и тот же объект Stream после терминальной операции?
    A)Да, стрим переиспользуется автоматически: после терминальной операции он сбрасывается в начало
    B)Нет — стрим одноразовый; повторный вызов бросит IllegalStateException
    C)Да, но нужно вызвать метод reset() перед повторным использованием, иначе результат будет пустым
    D)Да, стрим можно обходить сколько угодно раз, как обычную коллекцию List или Set в цикле
    показать ответ и разбор
    +B)Нет — стрим одноразовый; повторный вызов бросит IllegalStateException

    // разбор: Stream — ОДНОРАЗОВЫЙ конвейер, а не хранилище: после выполнения терминальной операции он считается «потраченным», и любая новая операция на нём бросает IllegalStateException: stream has already been operated upon or closed. Если данные нужно обойти дважды — создают новый стрим из источника (например, list.stream() ещё раз). Поэтому стрим не хранят в поле для многократного обхода — хранят коллекцию/Supplier<Stream>. Это отличает стрим от коллекции (которую можно перебирать многократно).

это 9 из 80

Ещё 71 вопросов по теме — в тренажёре, с движком повторения

Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.

Частые вопросы