Чем отличаются map, flatMap и foreach?
В Scala методы map, flatMap и foreach применяются к коллекциям и объектам, поддерживающим интерфейс Traversable, Option, Future и др. Несмотря на похожий синтаксис, они выполняют разные задачи и имеют разное поведение.
map
Метод map применяет переданную функцию ко всем элементам коллекции и возвращает новую коллекцию с тем же типом-контейнером, но с новыми элементами.
Сигнатура:
def map\[B\](f: A => B): C\[B\]
Пример:
val nums = List(1, 2, 3)
val doubled = nums.map(x => x \* 2)
// doubled: List(2, 4, 6)
Особенности:
-
Сохраняет структуру контейнера (List, Option, Future и т.п.).
-
Преобразует каждый элемент с помощью функции.
flatMap
Метод flatMap похож на map, но ожидается, что переданная функция возвращает вложенный контейнер. flatMap плоско разворачивает результат, то есть убирает один уровень вложенности.
Сигнатура:
def flatMap\[B\](f: A => Iterable\[B\]): Iterable\[B\]
Пример:
val words = List("a b", "c d")
val chars = words.flatMap(\_.split(" "))
// chars: List("a", "b", "c", "d")
Аналог через map:
val mapped = words.map(\_.split(" "))
// mapped: List(Array("a", "b"), Array("c", "d")) — вложенные массивы
Пример с Option:
val someNum: Option\[Int\] = Some(3)
val flatMapped = someNum.flatMap(x => Some(x \* 2))
// flatMapped: Some(6)
Особенности:
-
Используется, когда функция возвращает вложенный контейнер (List[List[T]], Option[Option[T]], и т.п.).
-
Убирает лишний уровень вложенности (flatten + map).
foreach
Метод foreach не возвращает результат, используется для побочных эффектов: вывода в консоль, изменения состояния, логирования и т.д.
Сигнатура:
def foreach\[U\](f: A => U): Unit
Пример:
val names = List("Alice", "Bob", "Charlie")
names.foreach(name => println(s"Привет, $name"))
Особенности:
-
Используется только ради выполнения действия, а не получения новой коллекции.
-
Возвращает Unit, не создаёт новых значений.
Сравнение на практике
Исходные данные:
val data = List(1, 2, 3)
map
val mapped = data.map(x => x \* 10)
// List(10, 20, 30)
flatMap
val flatMapped = data.flatMap(x => List(x, x \* 10))
// List(1, 10, 2, 20, 3, 30)
foreach
data.foreach(x => println(x \* 10))
// Выводит 10, 20, 30 в консоль, но не возвращает коллекцию
Применение в Option
map с Option
val opt = Some(5)
val mapped = opt.map(_ \* 2)
// mapped: Some(10)
flatMap с Option
val opt = Some(5)
val flatMapped = opt.flatMap(x => if (x > 0) Some(x \* 2) else None)
// flatMapped: Some(10)
foreach с Option
Some(5).foreach(x => println(s"Значение: $x"))
// Выводит "Значение: 5", но возвращает Unit
Применение в Future
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val fut = Future { 10 }
val mapped = fut.map(_ \* 2)
val flatMapped = fut.flatMap(x => Future(x \* 3))
fut.foreach(println)
-
map: преобразует результат асинхронной операции.
-
flatMap: позволяет строить цепочку зависимых Future.
-
foreach: вызывает побочный эффект после выполнения Future.
Когда использовать что?
Метод | Назначение | Возвращает |
---|---|---|
map | Преобразование каждого элемента | Коллекция того же типа |
--- | --- | --- |
flatMap | Преобразование с разворачиванием вложенности | Плоская коллекция |
--- | --- | --- |
foreach | Выполнение действия без создания новой коллекции | Unit (ничего) |
--- | --- | --- |
Пример с вложенной структурой
val data = List(Some(1), None, Some(2))
val mapped = data.map(opt => opt.map(_ \* 10))
// List(Some(10), None, Some(20))
val flatMapped = data.flatMap(opt => opt.map(_ \* 10))
// List(10, 20)
flatMap избавился от None, так как Option внутри List и разворачивание удаляет пустые значения.