Какая разница между методами map и flatMap в Java 8?

В Java 8, какая разница между Stream.map и Stream.flatMap?

Ответ 1

Оба map и flatMap могут быть применены к Stream<T>, и оба они возвращают a Stream<R>. Разница в том, что операция map создает одно выходное значение для каждого входного значения, тогда как операция flatMap производит произвольное число (ноль или более) значений для каждого входного значения.

Это отражается в аргументах каждой операции.

Операция map занимает Function, которая вызывается для каждого значения во входном потоке и выдает одно значение результата, которое отправляется в выходной поток.

Операция flatMap принимает функцию, которая концептуально хочет потреблять одно значение и производить произвольное количество значений. Однако в Java это громоздко для метода для возврата произвольного количества значений, поскольку методы могут возвращать только ноль или одно значение. Можно представить API, в котором функция mapper для flatMap принимает значение и возвращает массив или List значений, которые затем отправляются на выход. Учитывая, что это библиотека потоков, особенно подходящим способом представления произвольного количества возвращаемых значений является то, что сама функция mapper возвращает поток! Значения из потока, возвращаемого преобразователем, выводятся из потока и передаются в выходной поток. "Сгустки" значений, возвращаемых каждым вызовом функции сопоставления, вообще не различаются в выходном потоке, поэтому выход считается "сплющенным".

Типичное использование функции mapper flatMap для возврата Stream.empty(), если оно хочет отправить нулевые значения или что-то вроде Stream.of(a, b, c), если оно хочет вернуть несколько значений. Но, конечно, любой поток может быть возвращен.

Ответ 2

Stream.flatMap, как его можно угадать по его имени, представляет собой комбинацию операций map и a flat. Это означает, что вы сначала применяете функцию к своим элементам, а затем сглаживаете ее. Stream.map применяется только к потоку без выравнивания потока.

Чтобы понять, что такое сглаживание потока, рассмотрим структуру типа [ [1,2,3],[4,5,6],[7,8,9] ], которая имеет "два уровня". Сглаживание означает преобразование его в структуру "одного уровня": [ 1,2,3,4,5,6,7,8,9 ].

Ответ 3

Я хотел бы привести 2 примера, чтобы получить более практическую точку зрения:
Первый пример использования карты:

@Test
public void convertStringToUpperCaseStreams() {
    List<String> collected = Stream.of("a", "b", "hello") // Stream of String 
            .map(String::toUpperCase) // Returns a stream consisting of the results of applying the given function to the elements of this stream.
            .collect(Collectors.toList());
    assertEquals(asList("A", "B", "HELLO"), collected);
}   

Ничего особенного в первом примере, Function применяется для возврата String в верхнем регистре.

Второй пример использования flatMap:

@Test
public void testflatMap() throws Exception {
    List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) // Stream of List<Integer>
            .flatMap(List::stream)
            .map(integer -> integer + 1)
            .collect(Collectors.toList());
    assertEquals(asList(2, 3, 4, 5), together);
}

Во втором примере передается поток списка. Это не поток целых чисел!
Если необходимо использовать функцию преобразования (через карту), то сначала поток должен быть сведен к чему-то другому (поток целых чисел).
Если flatMap удален, то возвращается следующая ошибка: Оператор + не определен для типа (ов) аргумента List, int.
НЕЛЬЗЯ применить + 1 к списку целых чисел!

Ответ 4

Пожалуйста, пройдите по почте полностью, чтобы получить ясное представление,

карта против плоской карты:

Чтобы вернуть длину каждого слова из списка, мы сделаем что-то вроде ниже.

Короткая версия приведена ниже

Когда мы собираем два списка, приведенных ниже

Без плоской карты => [1,2], [1,1] => [[1,2], [1,1]] Здесь два списка размещены внутри списка, поэтому на выходе будет список, содержащий списки

С плоской картой => [1,2], [1,1] => [1,2,1,1] Здесь два списка сглаживаются и в список помещаются только значения, поэтому на выходе будет список, содержащий только элементы

В основном это объединяет все объекты в один

## Подробная версия была дана ниже: -

Например:-
Рассмотрим список ["STACK", "OOOVVVER"], и мы пытаемся вернуть список наподобие ["STACKOVER"] (возвращая только уникальные буквы из этого списка). Сначала мы будем делать что-то подобное ниже, чтобы вернуть список ["STACKOVER "] из [" СТЕК "," ОООВВВЕР "]

public class WordMap {
  public static void main(String[] args) {
    List<String> lst = Arrays.asList("STACK","OOOVER");
    lst.stream().map(w->w.split("")).distinct().collect(Collectors.toList());
  }
}

Здесь проблема в том, что лямбда, переданная методу карты, возвращает массив String для каждого слова, поэтому поток, возвращаемый методом карты, на самом деле имеет тип Stream, но нам нужен Stream для представления потока символов, ниже изображение иллюстрирует проблема.

Рисунок А:

enter image description here

Вы можете подумать, что мы можем решить эту проблему с помощью плоской карты,
Хорошо, давайте посмотрим, как решить эту проблему с помощью map и Arrays.stream Прежде всего вам понадобится поток символов вместо потока массивов. Существует метод с именем Arrays.stream(), который принимает массив и создает поток, например:

String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting word in to array of letters
    .map(Arrays::stream).distinct() //Make array in to separate stream
    .collect(Collectors.toList());

Выше все равно не работает, потому что теперь мы получаем список потоков (точнее, Stream>). Вместо этого мы должны сначала преобразовать каждое слово в массив отдельных букв, а затем превратить каждый массив в отдельный поток.

Используя flatMap, мы сможем решить эту проблему, как показано ниже:

String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting word in to array of letters
    .flatMap(Arrays::stream).distinct() //flattens each generated stream in to a single stream
    .collect(Collectors.toList());

flatMap будет выполнять сопоставление каждого массива не с потоком, а с содержимым этого потока. Все отдельные потоки, которые генерируются при использовании map (Arrays :: stream), объединяются в один поток. На рисунке B показан эффект использования метода flatMap. Сравните это с тем, что делает карта на рисунке A. Рисунок B enter image description here

Метод flatMap позволяет заменить каждое значение потока другим потоком, а затем объединяет все созданные потоки в один поток.

Ответ 5

Ответ в одну строку: flatMap помогает flatMap Collection<Collection<T>> в Collection<T>. Таким же образом, он также сведет Optional<Optional<T>> в Optional<T>.

enter image description here

Как видите, только с map():

  • Промежуточным типом является Stream<List<Item>>
  • Тип возвращаемого значения: List<List<Item>>

и с flatMap():

  • Промежуточным типом является Stream<Item>
  • Тип возвращаемого значения: List<Item>

Это результат теста из кода, использованного прямо ниже:

-------- Without flatMap() -------------------------------
     collect() returns: [[Laptop, Phone], [Mouse, Keyboard]]

-------- With flatMap() ----------------------------------
     collect() returns: [Laptop, Phone, Mouse, Keyboard]

Используемый код:

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class Parcel {
  String name;
  List<String> items;

  public Parcel(String name, String... items) {
    this.name = name;
    this.items = Arrays.asList(items);
  }

  public List<String> getItems() {
    return items;
  }

  public static void main(String[] args) {
    Parcel amazon = new Parcel("amazon", "Laptop", "Phone");
    Parcel ebay = new Parcel("ebay", "Mouse", "Keyboard");
    List<Parcel> parcels = Arrays.asList(amazon, ebay);

    System.out.println("-------- Without flatMap() ---------------------------");
    List<List<String>> mapReturn = parcels.stream()
      .map(Parcel::getItems)
      .collect(Collectors.toList());
    System.out.println("\t collect() returns: " + mapReturn);

    System.out.println("\n-------- With flatMap() ------------------------------");
    List<String> flatMapReturn = parcels.stream()
      .map(Parcel::getItems)
      .flatMap(Collection::stream)
      .collect(Collectors.toList());
    System.out.println("\t collect() returns: " + flatMapReturn);
  }
}

Ответ 6

Функция, которую вы передаете в stream.map, должна возвращать один объект. Это означает, что каждый объект во входном потоке дает ровно один объект в выходном потоке.

Функция, которую вы передаете в stream.flatMap, возвращает поток для каждого объекта. Это означает, что функция может возвращать любое количество объектов для каждого входного объекта (в том числе ни одного). Результирующие потоки затем объединяются в один выходной поток.

Ответ 7

для Карты мы имеем список элементов и a (function, action) f так:

[a,b,c] f(x) => [f(a),f(b),f(c)]

а для плоской карты - список элементов, и мы имеем функцию (функцию, действие) f и хотим, чтобы результат был сплющен:

[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]

Ответ 8

У меня такое чувство, что большинство ответов здесь затрудняют простую проблему. Если вы уже понимаете, как работает map, ее довольно легко понять.

Бывают случаи, когда мы можем в конечном итоге с нежелательными вложенными структурами при использовании map(), метод flatMap() предназначен для преодоления этого, избегая обертывания.


Примеры:

1

List<List<Integer>> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
  .collect(Collectors.toList());

Мы можем избежать вложенных списков с помощью flatMap:

List<Integer> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
  .flatMap(i -> i.stream())
  .collect(Collectors.toList());

2

Optional<Optional<String>> result = Optional.of(42)
      .map(id -> findById(id));

Optional<String> result = Optional.of(42)
      .flatMap(id -> findById(id));

где:

private Optional<String> findById(Integer id)

Ответ 9

В статье Oracle по опции подчеркивается это различие между map и flatmap:

String version = computer.map(Computer::getSoundcard)
                  .map(Soundcard::getUSB)
                  .map(USB::getVersion)
                  .orElse("UNKNOWN");

К сожалению, этот код не компилируется. Зачем? Переменный компьютер имеет тип Optional<Computer>, поэтому совершенно правильно называть карта метод. Однако getSoundcard() возвращает объект типа Необязательный. Это означает, что результатом операции карты является объект типа Optional<Optional<Soundcard>>. В результате, призыв к getUSB() недействителен, поскольку внешний список необязательно содержит value another Необязательно, что, конечно же, не поддерживает getUSB() Метод.

С потоками метод flatMap принимает функцию в качестве аргумента, который возвращает другой поток. Эта функция применяется к каждому элементу потока, что приведет к потоку потоков. Однако, flatMap имеет эффект замены каждого сгенерированного потока на содержимое этого потока. Другими словами, все отдельные потоки, которые генерируются функцией get, объединенной или "сплющенной" в одну один поток. То, что мы хотим здесь, похоже на нечто подобное, но мы хотим "сгладить" двухуровневую опцию в один.

Дополнительно также поддерживается метод flatMap. Его целью является применение функция преобразования на значение Необязательного (так же, как и карта операция), а затем сгладить полученный двухуровневый Необязательный в один.

Итак, чтобы сделать наш код правильным, мы должны переписать его следующим образом, используя flatMap:

String version = computer.flatMap(Computer::getSoundcard)
                   .flatMap(Soundcard::getUSB)
                   .map(USB::getVersion)
                   .orElse("UNKNOWN");

Первая flatMap гарантирует, что возвращается Optional<Soundcard>вместо Optional<Optional<Soundcard>>, а вторая плоская карта достигает той же цели, чтобы вернуть Optional<USB>. Обратите внимание, что третий вызов просто должен быть map(), потому что getVersion() возвращает Строка, а не необязательный объект.

http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Ответ 10

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

Представь, что у тебя есть яблоко. map превращает это яблоко в apple-juice например, или в однозначное сопоставление.

Возьмите то же самое яблоко и flatMap из него только семена, это то, что делает flatMap, или один ко многим, одно яблоко в качестве входных данных, много семян в качестве выходных.

Ответ 11

Map: - Этот метод принимает одну функцию в качестве аргумента и возвращает новый поток, состоящий из результатов, полученных при применении переданной функции ко всем элементам потока.

Предположим, у меня есть список целых значений (1,2,3,4,5) и один функциональный интерфейс, логика которого является квадратом переданного целого. (е → е * е).

List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> newList = intList.stream().map( e -> e * e ).collect(Collectors.toList());

System.out.println(newList);

вывод:-

[1, 4, 9, 16, 25]

Как вы можете видеть, выход представляет собой новый поток, значения которого являются квадратными значениями входного потока.

[1, 2, 3, 4, 5] -> apply e -> e * e -> [ 1*1, 2*2, 3*3, 4*4, 5*5 ] -> [1, 4, 9, 16, 25 ]

http://codedestine.com/java-8-stream-map-method/

FlatMap: - Этот метод принимает одну функцию в качестве аргумента, эта функция принимает один параметр T в качестве входного аргумента и возвращает один поток параметра R в качестве возвращаемого значения. Когда эта функция применяется к каждому элементу этого потока, она создает поток новых значений. Все элементы этих новых потоков, генерируемые каждым элементом, затем копируются в новый поток, который будет возвратным значением этого метода.

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

List<Student> studentList = new ArrayList<Student>();

  studentList.add(new Student("Robert","5st grade", Arrays.asList(new String[]{"history","math","geography"})));
  studentList.add(new Student("Martin","8st grade", Arrays.asList(new String[]{"economics","biology"})));
  studentList.add(new Student("Robert","9st grade", Arrays.asList(new String[]{"science","math"})));

  Set<Student> courses = studentList.stream().flatMap( e -> e.getCourse().stream()).collect(Collectors.toSet());

  System.out.println(courses);

вывод:-

[economics, biology, geography, science, history, math]

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

[S1, S2, S3] → [{"история", "математика", "география"}, {"экономика", "биология"}, {"наука", "математика"}] - > [экономика, биология, география, наука, история, математика]

http://codedestine.com/java-8-stream-flatmap-method/

Ответ 12

map() и flatMap()

  1. map()

Просто принимает Function лямбда-параметр, где T - элемент, а R - возвращаемый элемент, построенный с использованием T. В конце мы получим Stream с объектами типа R. Простой пример может быть:

Stream
  .of(1,2,3,4,5)
  .map(myInt -> "preFix_"+myInt)
  .forEach(System.out::println);

Он просто берет элементы с 1 по 5 типа Integer, использует каждый элемент для создания нового элемента из типа String со значением "prefix_"+integer_value и печатает его.

  1. flatMap()

Полезно знать, что flapMap() принимает функцию F<T, R> где

  • T - это тип, из которого можно построить поток из/с. Это может быть List (T.stream()), массив (Arrays.stream(someArray)) и т.д. Все, из чего может быть получен поток в/или форме. в приведенном ниже примере у каждого разработчика много языков, поэтому dev. Languages - это список и будет использовать лямбда-параметр.

  • R является результирующим потоком, который будет построен с использованием T. Зная, что у нас есть много экземпляров T, у нас, естественно, будет много потоков из R. Все эти потоки из типа R теперь будут объединены в один "плоский" поток из типа R,

пример

Примеры Бачири Тауфика, его ответ здесь прост и понятен. Просто для ясности, скажем так, у нас есть команда разработчиков:

dev_team = {dev_1,dev_2,dev_3}

каждый разработчик знает много языков:

dev_1 = {lang_a,lang_b,lang_c},
dev_2 = {lang_d},
dev_2 = {lang_e,lang_f}

Применение Stream.map() к dev_team для получения языков каждого разработчика:

dev_team.map(dev -> dev.getLanguages())

даст вам эту структуру:

{ 
  {lang_a,lang_b,lang_c},
  {lang_d},
  {lang_e,lang_f}
}

который в основном является List<List<Languages>>/Object[Languages[]]. Не очень красиво и не похоже на Java8!

с Stream.flatMap() вы можете "сгладить" вещи, как это берет вышеупомянутая структура
и превращает его в {lang_a, lang_b, lang_c, lang_d, lang_e, lang_f}, который может в основном использоваться как List<Languages>/Language[]/ect...

поэтому конец вашего кода будет иметь больше смысла, как это:

dev_team
   .stream()    /* {dev_1,dev_2,dev_3} */
   .map(dev -> dev.getLanguages()) /* {{lang_a,...,lang_c},{lang_d}{lang_e,lang_f}}} */
   .flatMap(languages ->  languages.stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
   .doWhateverWithYourNewStreamHere();

или просто:

dev_team
       .stream()    /* {dev_1,dev_2,dev_3} */
       .flatMap(dev -> dev.getLanguages().stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
       .doWhateverWithYourNewStreamHere();

Когда использовать map() и flatMap():

  • Используйте map() когда каждый элемент типа T из вашего потока должен быть отображен/преобразован в один элемент типа R. Результатом является отображение типа (1 начальный элемент → 1 конечный элемент) и нового потока элементов типа R возвращается.

  • Используйте flatMap() когда каждый элемент типа T из вашего потока должен отображаться/преобразовываться в Коллекции элементов типа R. Результатом является отображение типа (1 начальный элемент → n конечных элементов). Эти коллекции затем объединяются (или сплющиваются) с новым потоком элементов типа R. Это полезно, например, для представления вложенных циклов.

До Java 8:

List<Foo> myFoos = new ArrayList<Foo>();
    for(Foo foo: myFoos){
        for(Bar bar:  foo.getMyBars()){
            System.out.println(bar.getMyName());
        }
    }

Post Java 8

myFoos
    .stream()
    .flat(foo -> foo.getMyBars().stream())
    .forEach(bar -> System.out.println(bar.getMyName()));

Ответ 13

Также хорошая аналогия может быть с С#, если вы знакомы. В основном С# Select похож на java map и С# SelectMany java flatMap. То же самое относится к Kotlin для коллекций.

Ответ 14

Это очень запутанно для начинающих. Основное различие map испускает один элемент для каждой записи в списке, а flatMap - это в основном операция map + flatten. Чтобы быть более понятным, используйте flatMap, когда вам требуется более одного значения, например, когда вы ожидаете, что цикл будет возвращать массивы, flatMap будет действительно полезен в этом случае.

Я написал блог об этом, вы можете проверить его здесь.

Ответ 15

Простой ответ

Операция map может создать Stream Stream.EX Stream<Stream<Integer>>

Операция flatMap будет производить только Stream чего-либо. EX Stream<Integer>

Ответ 16

Различие становится ясным, если взять простой пример:

List<List<Integer>> matrix = List.of(List.of(1, 2), List.of(3, 4));

Цель: напечатать все значения матрицы (каким-либо образом).

flatMap

Возьмем Function которая принимает один аргумент и создает Stream:

Function<List<Integer>, Stream<Integer>> fn = list -> list.stream();

И примените эту функцию к каждому элементу (списку) в матрице:

for (List<Integer> list : matrix) {
    Stream<Integer> stream = fn.apply(list);
    stream.forEach(System.out::print);
}

Это именно то, что может сделать Stream.flatMap(fn) в сжатом виде:

matrix.stream().flatMap(fn).forEach(System.out::print);

ВАЖНО: fn.apply(list) - принимает List<Integer> и создает Stream<Integer>.

карта

Возьмем Function которая принимает один аргумент и выдает value:

Function<List<Integer>, String> fn = list -> list.toString();

И примените эту функцию к каждому элементу (списку) в матрице:

for (List<Integer> list : matrix) {
    String str = fn.apply(list);
    System.out.print(str);
}

Это именно то, что Stream.map(fn) может выполнить в сжатом виде:

matrix.stream().map(fn).forEach(System.out::print);

ВАЖНО: fn.apply(list) - принимает List<Integer> и создает String.

Ответ 17

Операции потока flatMap и map принимают функцию в качестве входных данных.

flatMap ожидает, что функция вернет новый поток для каждого элемента потока и вернет поток, который объединяет все элементы потоков, возвращаемых функцией для каждого элемента. Другими словами, с помощью flatMap для каждого элемента из источника будет создана несколько элементов. http://www.zoftino.com/java-stream-examples#flatmap-operation

map ожидает, что функция вернет преобразованное значение и вернет новый поток, содержащий преобразованные элементы. Другими словами, с map, для каждого элемента из источника, один преобразованный элемент будет создан функцией. http://www.zoftino.com/java-stream-examples#map-operation

Ответ 18

flatMap() также использует частичную ленивую оценку потоков. Он будет читать первый поток и только при необходимости перейдет к следующему потоку. Поведение подробно объясняется здесь: Гарантируется ли flatMap ленивость?

Ответ 19

Карта и плоская карта

Сначала понимаем карту,

def rollDie = nonNegativeLessThan (6)

Теперь этот func возвращает rand num b/w 0-5, но мы хотим, чтобы диапазон был b/w 1-6 Поэтому мы применили бы на нем функцию map func, которая будет применять некоторые функции для каждого элемента, и мы получим диапазон желаний.

def rollDie = map (nonNegativeLessThan (6)) (i = > я + 1)

Теперь идет flatMap так как имя этого func будет faltten значение в соответствии с вашим типом возврата и будет отображать его и даст тот же результат.

Теперь предположим, что у вас две кости, и вы хотите получить тот же результат, но каждый раз два результата вместо одного.

теперь вышеуказанная функция не будет работать, если мы используем карту, она даст список кортежей. Или вы можете использовать flatmap.That сгладит кортеж к ints и List [Tuple] станет List [Ints]

Поскольку имя сначала сглаживается и на нем наносится карта.

Ответ 20

map() в Java 8

поток, состоящий из результатов применения данной функции к элементам этого потока. Карта принимает входные данные, которые описывают, как значение должно быть преобразовано в. Предположим, мы хотим узнать возраст ученика, которого зовут Саурабх, до сих пор мы только извлекали весь объект из потока, но как нам это сделать? Мы можем использовать map(), чтобы преобразовать Student Stream в возрастной поток, как показано ниже.

int age = students.stream()
    .filter(student -> SAURABH.equals(student.getName()))
    .map(Student::getAge)
    .findAny()
    .orElse(0);
System.out.printf("*** Age of %s is %d\n",SAURABH, age);

Теперь давайте попробуем получить все имена студентов с помощью метода collect().

Set<String> names = students.stream()
       .map(Student::getName) // this will convert the Student Stream into String Stream by 
        // applying the getName()
       .collect(Collectors.toSet());  
System.out.printf("*** All the names from the list is %s\n",names);

map() против flatMap()

Предположим, что мы хотим получить все доступные курсы в списке студентов, тогда мы можем написать код, как показано ниже:

Set<String> courses = students.stream()
         .map(Student::getCourses)
         .collect(Collectors.toSet())

** Здесь мы получим ошибку компиляции, как показано ниже

Несоответствие типов: невозможно преобразовать из Set в Set Для решения этой проблемы мы используем flatMap() **

flatMap() в Java 8

Он возвращает поток, состоящий из результатов замены каждого элемента этого потока содержимым отображенного потока, созданного путем применения предоставленной функции отображения к каждому элементу. flatMap преобразует поток потока в простой поток. В приведенном ниже примере мы используем flatMap для преобразования массива Stream в поток String.

Set<String> courses = students.stream()
         .map(Student::getCourses)
         .flatMap(Arrays::stream)
         .collect(Collectors.toSet());

Для получения дополнительной информации вы можете обратиться по ссылкам ниже:

https://onlyfullstack.blogspot.com/2018/12/map-vs-flatmap-in-java-8.html

http://onlyfullstack.blogspot.com/p/java-8-features.html