it-swarm.com.ru

В чем разница между методами map и flatMap в Java 8?

В Java 8, в чем разница между Stream.map и Stream.flatMap методами?

562
cassiomolin

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

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

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

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

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

678
Stuart Marks

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

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

338
Dici

Я хотел бы привести 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 к списку целых чисел!

203
Rudy Vissers

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

карта против flatMap:

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

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

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

Без плоская карта => [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 для представления потока символов, ниже изображение иллюстрирует проблема.

Рисунок A:

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>). Вместо этого мы должны сначала преобразовать каждое слово Word в массив отдельных букв, а затем сделать каждый массив отдельным потоком.

Используя 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 позволяет заменить каждое значение потока другим потоком, а затем объединяет все созданные потоки в один поток.

122
TechDog

Ответ в одну строку: 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);
  }
}
66
nxhoaf

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

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

38
Philipp

для карты у нас есть список элементов и (функция, действие) 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)]
25
Bachiri Taoufiq Abderrahman

У меня такое ощущение, что большинство ответов здесь усложняют простую проблему. Если вы уже понимаете, как работает 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)
23
Grzegorz Piwowarek

В статье Oracle, посвященной Optional, подчеркивается это различие между картой и плоской картой:

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

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

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

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

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

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

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

http://www.Oracle.com/technetwork/articles/Java/java8-optional-2175753.html

18
Rusty Core

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

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

Возьмите то же самое Apple и ​​получите из него только семена, то есть то, что делает flatMap, или один ко многим, один Apple в качестве входных данных, много семена в качестве выхода.

11
Eugene

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

Давайте представим, у меня есть список целочисленных значений (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/

10
lalitbhagtani

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. Это полезно, например, для представления вложенные циклы .

Pre Java 8:

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

Сообщение Java 8

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

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

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

1
Niraj Chauhan

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

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

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

1
Melad Basilius

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

1
Arsenius

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

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

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

0
Arnav Rao

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

0
S.K.