В чем разница между методами map() и flatMap() в Java 8 Stream?
Основное различие между map() и flatMap()
Ключевое отличие в том, что map() преобразует каждый элемент потока ровно в один элемент выходного потока (отношение 1:1), тогда как flatMap() преобразует каждый элемент в ноль или более элементов (отношение 1:N) и "разворачивает" их в плоский поток.
Почему ваш пример с map() не работает
В вашем коде word.split("") возвращает массив String[]. Метод map() принимает функцию Function<T, R>, где T — входной тип, а R — выходной. В результате вы получаете поток массивов Stream<String[]>, а не поток символов. Когда вы выводите массив через System.out.println, Java вызывает toString() массива, который дает тот самый хэш-код.
Решение с flatMap()
List<String> words = Arrays.asList("Hello", "World");
List<String> letters = words.stream()
.flatMap(word -> Arrays.stream(word.split(""))) // разворачиваем массив в поток строк
.collect(Collectors.toList());
System.out.println(letters); // [H, e, l, l, o, W, o, r, l, d]
Как работает flatMap()
Метод flatMap() принимает функцию Function<T, Stream<R>>. Эта функция должна возвращать поток для каждого входного элемента. Затем flatMap() "сплющивает" все эти потоки в один общий поток.
Визуально процесс выглядит так:
- "Hello" → Stream("H", "e", "l", "l", "o")
- "World" → Stream("W", "o", "r", "l", "d")
- Результат: Stream("H", "e", "l", "l", "o", "W", "o", "r", "l", "d")
Более сложный пример с Optional
public class Person {
private String name;
private List<String> phoneNumbers;
// getters, setters
}
List<Person> people = ...;
// map() - получаем список списков телефонов
List<List<String>> phoneLists = people.stream()
.map(Person::getPhoneNumbers)
.collect(Collectors.toList());
// flatMap() - получаем плоский список всех телефонов
List<String> allPhones = people.stream()
.flatMap(person -> person.getPhoneNumbers().stream())
.collect(Collectors.toList());
Когда что использовать
- map() — когда каждому элементу соответствует ровно один выходной элемент. Примеры: преобразование строк в их длины, извлечение одного поля объекта, математические преобразования.
- flatMap() — когда один элемент может породить несколько элементов (или ни одного). Примеры: разбиение строк, работа с вложенными коллекциями, обработка Optional, развертывание иерархических структур.
Практический пример с Optional
List<Optional<String>> optionals = Arrays.asList(
Optional.of("Java"),
Optional.empty(),
Optional.of("8")
);
// С flatMap мы легко отфильтровываем пустые значения
List<String> values = optionals.stream()
.flatMap(Optional::stream) // Java 9+ или .filter(Optional::isPresent).map(Optional::get)
.collect(Collectors.toList());
System.out.println(values); // [Java, 8]
Важное замечание
Метод flatMap() требует, чтобы ваша функция возвращала именно Stream. Если у вас есть массив, используйте Arrays.stream(). Если коллекция — collection.stream(). Это частая ошибка новичков.
Альтернативный взгляд: понимание через типы
Проще всего понять разницу, если посмотреть на сигнатуры методов:
Stream<R> map(Function<? super T, ? extends R> mapper)Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
map() берет элемент типа T и возвращает элемент типа R. flatMap() берет элемент типа T и возвращает поток элементов типа R.
Пример с числами
List<Integer> numbers = Arrays.asList(1, 2, 3);
// map: каждое число -> одно число * 2
List<Integer> doubled = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList()); // [2, 4, 6]
// flatMap: каждое число -> диапазон чисел
List<Integer> expanded = numbers.stream()
.flatMap(n -> IntStream.rangeClosed(1, n).boxed())
.collect(Collectors.toList()); // [1, 1, 2, 1, 2, 3]
Дополнительный совет
Если вы сомневаетесь, какой метод использовать, задайте себе вопрос: "Может ли один входной элемент породить несколько выходных?". Если да — используйте flatMap(), если нет — map(). Это правило работает в 99% случаев.
Ответов пока нет.
Войдите чтобы оставить ответ