Stream.map: примеры (JAVA)

Примеры использования Stream.map в коде
Раздел: Потоки данных (Stream API) - промежуточные операции
Stream.map(Function mapper): Stream

Описание метода Stream.map

Метод map в пакете java.util.stream применяется для преобразования элементов потока одного типа в элементы другого типа с помощью функции отображения. Оригинальный сигнатурный вид в интерфейсе Stream:

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

Параметры и возвращаемые значения:

  • mapper: функция типа Function<? super T, ? extends R>. Принимает элемент потока типа T и возвращает значение типа R. Функция не должна изменять внешнее состояние (предпочтительно), может выбросить исключение.
  • Возвращает Stream<R>: ленивый последовательный/параллельный поток, в котором каждый элемент - результат применения mapper к соответствующему элементу входного потока.

Примеры специализированных версий для примитивных потоков:

  • IntStream.map(IntUnaryOperator mapper)
  • LongStream.map(LongUnaryOperator mapper)
  • DoubleStream.map(DoubleUnaryOperator mapper)
  • Для преобразования между видами предусмотрены mapToInt, mapToLong, mapToDouble, mapToObj.

Особенности поведения:

  • Операция ленивa: mapper применяется только при терминальной операции.
  • Не является короткозамыкающей: обработка всех элементов не прекращается раньше терминальной операции (если сама терминальная операция не короткозамыкающая).
  • Поддерживает параллельное выполнение, mapper должен быть безопасен для параллельного вызова.
  • Поток не должен содержать null - многие реализации и операции могут привести к NullPointerException, если mapper вернул null или входные элементы равны null.

Ниже приведены практические примеры в секции «examples».

Короткие примеры использования

Примеры демонстрируют код и результат.

1. Простое преобразование строк в верхний регистр

List<String> list = List.of("one", "two", "three");
List<String> upper = list.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
System.out.println(upper);
[ONE, TWO, THREE]

2. Преобразование в длины (IntStream через mapToInt)

List<String> words = List.of("a", "bb", "ccc");
int sum = words.stream()
    .mapToInt(String::length)
    .sum();
System.out.println(sum);
6

3. map с методом, возвращающим объект другого типа

record Person(String name, int age) {}
List<Person> people = List.of(new Person("Anna", 30), new Person("Boris", 25));
List<String> names = people.stream()
    .map(Person::name)
    .collect(Collectors.toList());
System.out.println(names);
[Anna, Boris]

4. map на примитивный поток и обратно

IntStream.range(1, 4)
    .map(i -> i * 2)           // IntStream
    .mapToObj(Integer::toString) // Stream<String>
    .forEach(System.out::println);
2
4
6

5. Использование метода, возвращающего null (показан NPE)

List<String> data = List.of("x", "y");
List<String> res = data.stream()
    .map(s -> null)
    .collect(Collectors.toList());
System.out.println(res);
[null, null]  // но дальнейшие операции с null могут привести к NullPointerException

Похожие методы в Java и их особенности

  • flatMap
  • Возвращает для каждого входного элемента поток элементов и затем "разворачивает" их в единый поток. Предпочтительнее, когда требуется получить ноль, один или несколько элементов из одного входного.

  • mapMulti
  • Добавляет возможность более эффективного вывода нескольких элементов из одного входного (доступно в новых версиях Java). Удобнее и производительнее, чем реализация flatMap с коллекциями в ряде случаев.

  • mapToInt / mapToLong / mapToDouble
  • Специализированные версии для примитивов: уменьшают автоупаковку, повышают производительность и доступ к статистическим методам примитивных стримов.

  • peek
  • Не трансформирует тип; используется для отладки и побочных действий; не подходит для преобразований.

Выбор между ними зависит от требуемого результата: если нужен один объект на вход - map; несколько объектов - flatMap или mapMulti; если нужен примитивный результат - mapToInt и аналоги.

Аналоги в других языках и особенности

  • JavaScript
  • const arr = [1,2,3];
    const doubled = arr.map(x => x*2);
    console.log(doubled);
    [2,4,6]

    Array.prototype.map выполняет синхронно, возвращая новый массив; нет ленивости, в отличие от Stream.

  • Python
  • nums = [1,2,3]
    doubled = list(map(lambda x: x*2, nums))
    print(doubled)
    [2, 4, 6]

    map возвращает итератор в Python 3 (ленивость), однако обычно используется list comprehension для читаемости: [x*2 for x in nums].

  • PHP
  • $arr = [1,2,3];
    $res = array_map(fn($x) => $x*2, $arr);
    print_r($res);
    Array ( [0] => 2 [1] => 4 [2] => 6 )

    array_map сразу возвращает массив, нет встроенной ленивости.

  • SQL
  • SELECT UPPER(name) FROM users;
    -- Результат: столбец с преобразованными значениями

    Проекция в SQL эквивалентна map на множестве строк.

  • C# (LINQ)
  • var doubled = new[]{1,2,3}.Select(x => x*2);
    Console.WriteLine(string.Join(",", doubled));
    2,4,6

    LINQ Select ленивый как Stream и часто более выразителен в цепочках.

  • Kotlin
  • val doubled = listOf(1,2,3).map { it * 2 }
    println(doubled)
    [2, 4, 6]

    Коллекции Kotlin имеют eager map; Sequence.map - ленивый аналог.

  • Go
  • // В Go часто используется явный цикл
    in := []int{1,2,3}
    out := make([]int, len(in))
    for i,v := range in { out[i] = v*2 }
    fmt.Println(out)
    [2 4 6]

    Отсутствует встроенная функциональная map в стандартной библиотеке; предпочитаются циклы.

  • Lua
  • local t={1,2,3}
    local out={}
    for i,v in ipairs(t) do out[i]=v*2 end
    print(table.concat(out,","))
    2,4,6

    Реализация вручную через циклы.

Ключевые отличия от Java: наличие или отсутствие ленивости, специализация для примитивов, синтаксис и поточная семантика.

Типичные ошибки при использовании map

  • Возвращение null из mapper
  • List<String> l = List.of("a");
    l.stream().map(s -> null).forEach(System.out::println);
    null

    Хотя выводит null, дальнейшие операции, ожидающие неподложенные значения, могут привести к NPE.

  • Побочные эффекты внутри mapper
  • List<Integer> src = List.of(1,2,3);
    List<Integer> out = new ArrayList<>();
    src.stream().map(i -> { out.add(i); return i; }).count();
    System.out.println(out);
    [1,2,3]  // но поведение при параллельном потоке не безопасно

    При параллельном выполнении могут возникнуть состояния гонки. Лучше избегать побочных эффектов.

  • Использование map для фильтрации
  • List<String> s = List.of("a","","b");
    List<String> r = s.stream().map(str -> str.isEmpty() ? null : str)
        .collect(Collectors.toList());
    ["a", null, "b"]

    Лучше использовать filter для исключения элементов вместо создания null-значений.

  • Повторное использование потока
  • Stream<Integer> st = Stream.of(1,2,3).map(i -> i*2);
    st.forEach(System.out::println);
    st.forEach(System.out::println);
    1 раз пройдет, второе вызовет IllegalStateException: stream has already been operated upon or closed
  • ClassCastException при небезопасных преобразованиях
  • Object o = "text";
    Stream.of(o).map(x -> (Integer)x).forEach(System.out::println);
    Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

Изменения и эволюция метода

Метод map является частью Streams API с Java 8 и сохраняет стабильный контракт. Ключевые моменты в развитии экосистемы:

  • Специализированные примитивные потоки (IntStream, LongStream, DoubleStream) появились одновременно с API и поддерживают свои версии map.
  • В новых релизах Java добавлены утилиты для потоков, например Stream.toList() (Java 16) и mapMulti (в одном из последних релизов Java 16+), предоставляющие улучшенные и более эффективные сценарии вывода нескольких элементов из одного входного.
  • Оптимизации реализации и исправления производительности происходили в последующих версиях JDK, но семантика map осталась прежней.

Расширенные и редко встречающиеся сценарии

1. map с индексом (через IntStream.range)

Пример java
List<String> names = List.of("a","b","c");
List<String> numbered = IntStream.range(0, names.size())
    .mapToObj(i -> i + ": " + names.get(i))
    .collect(Collectors.toList());
System.out.println(numbered);
[0: a, 1: b, 2: c]

2. mapMulti - эффективная генерация нескольких элементов

Пример java
List<String> data = List.of("ab", "c");
List<String> chars = data.stream()
    .mapMulti((s, consumer) -> { for (char ch : s.toCharArray()) consumer.accept(String.valueOf(ch)); })
    .collect(Collectors.toList());
System.out.println(chars);
[a, b, c]

3. Преобразование и агрегация в Map с конфликтом ключей

Пример java
List<String> items = List.of("a","A","b","B");
Map<String, String> map = items.stream()
    .map(String::toLowerCase)
    .collect(Collectors.toMap(s -> s, s -> s, (a,b) -> a));
System.out.println(map);
{a=a, b=b}

4. Обработка исключений в mapper

Пример java
List<String> in = List.of("1","x","3");
List<Integer> out = in.stream()
    .map(s -> {
        try { return Integer.parseInt(s); }
        catch(NumberFormatException e) { return -1; }
    })
    .collect(Collectors.toList());
System.out.println(out);
[1, -1, 3]

5. Ленивые бесконечные потоки и map

Пример java
Stream.iterate(0, n -> n+1)
    .map(n -> n*n)
    .filter(n -> n < 10)
    .limit(5)
    .forEach(System.out::println);
0
1
4
9
16

6. Параллельный map с безопасными операциями

Пример java
List<Integer> in = IntStream.range(0,1000).boxed().collect(Collectors.toList());
List<Integer> out = in.parallelStream()
    .map(i -> i * 2)
    .collect(Collectors.toList());
System.out.println(out.size());
1000

Для корректной работы в параллели mapper не должен иметь побочных эффектов и зависеть от изменяемого внешнего состояния.

7. Преобразование в CompletableFuture и последовательная сборка

Пример java
List<String> urls = List.of("u1","u2");
List<CompletableFuture<String>> futures = urls.stream()
    .map(url -> CompletableFuture.supplyAsync(() -> "content:" + url))
    .collect(Collectors.toList());
List<String> contents = futures.stream()
    .map(CompletableFuture::join)
    .collect(Collectors.toList());
System.out.println(contents);
[content:u1, content:u2]

8. map с изменением структуры, затем groupingBy

Пример java
List<String> names = List.of("Ann","Bob","Amy");
Map<Character, List<String>> byFirst = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.groupingBy(s -> s.charAt(0)));
System.out.println(byFirst);
{A=[ANN, AMY], B=[BOB]}

9. Отличие map и flatMap на реальном примере

Пример java
List<List<Integer>> data = List.of(List.of(1,2), List.of(3));
var wrong = data.stream().map(x -> x).collect(Collectors.toList());
var flat = data.stream().flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(wrong);
System.out.println(flat);
[[1, 2], [3]]
[1, 2, 3]

Варианты показывают, как сочетать map с другими операциями для получения эффективных и безопасных конвейеров обработки.

джава Stream.map function comments

En
Stream.map Returns a stream consisting of the results of applying the given function to the elements