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

Ответ

Метод map в Stream<T> ожидает аргумент типа Function<? super T, ? extends R>.

Разбор интерфейса:

  • T — тип элемента исходного потока (контравариантный аргумент).
  • R — тип элемента результирующего потока (ковариантный результат).
  • Единственный абстрактный метод (SAM) интерфейса FunctionR apply(T t).

Суть операции: Преобразование каждого элемента потока из типа T в тип R по правилу, заданному функцией (отображение 1:1).

Пример: Преобразование строк в их длины

List<String> names = List.of("John", "Alice", "Bob");
List<Integer> nameLengths = names.stream()
    // Function<String, Integer>: строка -> её длина
    .map(String::length)
    .toList(); // Результат: [4, 5, 3]

Аналоги в других API:

  • Optional<T>: Optional.map(Function<T, R>) преобразует значение внутри Optional, если оно присутствует.
  • CompletableFuture<T>: thenApply(Function<T, R>) для синхронного преобразования результата асинхронной операции.

Важно: map — это промежуточная операция, она не вызывает выполнения потока и возвращает новый ленивый поток с преобразованными элементами.

Ответ 18+ 🔞

Слушай, вот этот самый map в стримах — это ж классика, блядь! Представь себе конвейер, по которому едут какие-то хуёвинки типа T. А map — это такой станок, который каждую хуёвинку T хватает, переделывает в другую хуёвинку R и кладёт на следующий конвейер. Одна штука на вход — одна, но уже другая, на выход. Просто, как три копейки, ёпта!

А сигнатура у него, конечно, пиздец какая страшная: Function<? super T, ? extends R>. Но если разобраться, то всё логично, блядь.

  • ? super T — значит, можно запихнуть функцию, которая жрёт не только конкретно T, но и его предков. Хуй с ним, пусть жрёт.
  • ? extends R — значит, функция может возвращать не только R, но и его детей. Ну и ладно, главное, что в R оно потом влезет.

А внутри у этой функции один-единственный метод apply(T t), который и делает всю магию. Всё, больше от тебя ничего не надо, блядь.

Вот смотри, как это на практике выглядит. Берём список имён и делаем из них список длин этих имён. Ебать мои старые костыли, какая же это частая операция!

List<String> names = List.of("John", "Alice", "Bob");
List<Integer> nameLengths = names.stream()
    // Вот тут и живёт наша функция: берёт строку, выёбывается с ней, возвращает число
    .map(String::length)
    .toList(); // И получаем мы, сука, [4, 5, 3]

И ведь эта штука, map, она везде пролезла, как хитрая жопа! Ты глянь:

  • В Optional<T> — там тоже есть map, который ковыряется в значении, если оно внутри есть. Нет значения — и похуй, ничего не делает.
  • В CompletableFuture<T> — там thenApply это тот же самый map, только для асинхронных пиздюлей. Дождался результата — примени функцию.

И главное, запомни: map — это операция-лентяй, промежуточная. Она не запускает весь этот конвейер в пизду. Она просто говорит: «Ладно, я запомнил, что надо будет каждую хуйню преобразовать вот так». А реальная работа начнётся только когда ты позовёшь какую-нибудь терминальную операцию вроде toList() или collect. Вот тогда-то всё и завертится, блядь!