С каким функциональным интерфейсом работает метод map в Stream API?

Ответ

Метод map() в java.util.stream.Stream работает с функциональным интерфейсом java.util.function.Function<T, R>.

Детали интерфейса Function<T, R>:

  • Абстрактный метод: R apply(T t) — принимает аргумент типа T и возвращает результат типа R.
  • Назначение в map(): Задать правило преобразования каждого элемента исходного потока (Stream<T>) в элемент нового потока (Stream<R>).

Почему это важно? Использование стандартного интерфейса Function обеспечивает совместимость с лямбда-выражениями и ссылками на методы, что делает код лаконичным.

Пример с лямбдой и ссылкой на метод:

List<String> names = List.of("John", "Alice", "Bob");

// Использование лямбда-выражения
List<String> upperNames1 = names.stream()
    .map(name -> name.toUpperCase()) // Function<String, String>
    .toList();

// Использование ссылки на метод (более идиоматично)
List<String> upperNames2 = names.stream()
    .map(String::toUpperCase) // Тот же Function<String, String>
    .toList();

Ключевые свойства map():

  1. Не модифицирует источник: Работает по принципу immutability.
  2. Ленивая операция: Вычисление происходит только при вызове терминальной операции (например, collect() или toList()).
  3. Сохраняет порядок: В последовательных потоках порядок элементов сохраняется.

Ответ 18+ 🔞

Ну ты смотри, какой народ-то пошёл! Приходит человек к Stream.map(), а там, блядь, целый интерфейс Function<T, R> торчит, как манда с ушами. И думает: «Ёпта, что за хуйня?». А это, оказывается, не просто так, а специально, чтобы твои мозги не взорвались от овердохуища разных вариантов.

Короче, слушай сюда, распиздяй. Этот Function<T, R> — он как универсальная инструкция: «Вот тебе штука типа T, сделай из неё штуку типа R и отъебись». Всё, что ему надо — это один ёбаный метод R apply(T t). Всё! Больше от тебя нихуя не нужно.

И вся магия в том, что из-за этой простоты можно делать вот такие, с позволения сказать, фокусы:

List<String> names = List.of("John", "Alice", "Bob");

// Вариант для тех, кто любит расписывать всё по полочкам, как будто заявление в ЖЭК пишешь
List<String> upperNames1 = names.stream()
    .map(name -> name.toUpperCase()) // Смотри-ка, лямбда! name -> хуяк -> NAME
    .toList();

// А это вариант для ленивых гениев, которые поняли, что можно ещё короче
List<String> upperNames2 = names.stream()
    .map(String::toUpperCase) // Во, блядь! Ссылка на метод! Красота же, ёпта!
    .toList();

Видишь? Оба варианта — это один и тот же Function<String, String>. Просто во втором случае компилятор, хитрая жопа, сам догадывается, что к чему.

А теперь, чтобы ты совсем не обосрался с дуру, запомни три вещи про map():

  1. Он ничего не портит в исходнике. Взял поток, нахуйчил из него новый, а старый как лежал нетронутым, так и лежит. Immutability, блядь, святое!
  2. Он ленивый, как мой кот сука собака. Пока ты не скажешь «соберись, падла!» (типа collect() или toList()), он нихуя делать не будет. Просто запомнит, что надо сделать.
  3. Порядок он соблюдает. Если в потоке шли «Вася», «Петя», «Маша», то и после map() они так и пойдут, только, например, «ВАСЯ», «ПЕТЯ», «МАША». Никакого «Маша, Вася, Петя» не будет, если поток последовательный.

Вот и вся философия. Не так страшен чёрт, как его java.util.function.