Jaka jest różnica między metodami map () i flatMap () w Javie 8?

705

W Javie 8, co jest różnica między Stream.map()i Stream.flatMap()metody?

kasiomolina
źródło
55
Podpis typu mówi całą historię. map :: Stream T -> (T -> R) -> Stream R, flatMap :: Stream T -> (T -> Stream R) -> Stream R.
Chris Martin
97
fwiw, te podpisy typu nawet nie wyglądają jak Java. (Wiem, wiem - ale żeby powiedzieć „cała historia” wrt map / flatMap zakłada dużą wiedzę na temat nowej i ulepszonej wersji „Java ++”)
Michael
16
@michael Podpis tego typu wygląda jak Haskell, a nie Java. Ale to nie jest jasne, czy rzeczywisty podpis Java jest bardziej czytelny: <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper).
Stuart Marks
7
Ha, tak, miałem na myśli „rzeczywistą Javę”. Podobnie jak C ++, współczesna Java jest prawie nie do poznania dla każdego, kto zaczął z niej korzystać w latach 90. (tak jak ja, oba języki). Odpowiadając tylko na komentarz, podpisy tej metody prawie nie mówią „całej historii”, przynajmniej nie więcej, nie bez dodatkowej prezentacji (lub w tym przypadku komentatorów, tłumaczenia).
Michael
2
Oznacza to, mapże lambda odwzorowująca zwraca R, a flatMaplambda odwzorowująca zwraca a Streamz R( Stream<R>). Strumienie zwrócone przez program flatMapodwzorowujący są skutecznie łączone. W przeciwnym razie zarówno mapi flatMappowrót Stream<R>; różnica jest co zwróci lambdas Mapper Rvs. Stream<R>.
derekm

Odpowiedzi:

814

Oba mapi flatMapmogą być zastosowane do a Stream<T>i oba zwracają a Stream<R>. Różnica polega na tym, że mapoperacja generuje jedną wartość wyjściową dla każdej wartości wejściowej, podczas gdy flatMapoperacja generuje dowolną liczbę (zero lub więcej) wartości dla każdej wartości wejściowej.

Odzwierciedlają to argumenty każdej operacji.

mapOperacja trwa Function, która jest wywoływana dla każdej wartości w strumieniu wejściowym i wytwarza jedną wartość wynikową, który jest wysyłany do strumienia wyjściowego.

flatMapOperacja trwa funkcję, która koncepcyjnie chce spożywać jedną wartość i produkować dowolną liczbę wartości. Jednak w Javie niewygodne jest, aby metoda zwracała dowolną liczbę wartości, ponieważ metody mogą zwracać tylko zero lub jedną wartość. Można sobie wyobrazić interfejs API, w którym funkcja mapująca dla flatMapprzyjmuje wartość i zwraca tablicę lub aListwartości, które są następnie wysyłane do wyjścia. Biorąc pod uwagę, że jest to biblioteka strumieni, szczególnie trafnym sposobem reprezentowania dowolnej liczby zwracanych wartości jest, aby sama funkcja mapująca zwróciła strumień! Wartości ze strumienia zwrócone przez program odwzorowujący są usuwane ze strumienia i przekazywane do strumienia wyjściowego. „Gromady” wartości zwracane przez każde wywołanie funkcji odwzorowującej wcale nie są rozróżniane w strumieniu wyjściowym, dlatego mówi się, że dane wyjściowe zostały „spłaszczone”.

Typowe zastosowanie to funkcja odwzorowująca flatMapzwracająca, Stream.empty()jeśli chce wysłać wartości zerowe, lub coś w rodzaju, Stream.of(a, b, c)jeśli chce zwrócić kilka wartości. Ale oczywiście każdy strumień może zostać zwrócony.

Znaki Stuarta
źródło
26
Wydaje mi się, że flatMapoperacja jest dokładnym przeciwieństwem mieszkania. Po raz kolejny pozostawmy informatykom, aby zawrócili termin na głowie. Podobnie jak funkcja „przezroczysta”, co oznacza, że ​​nie widzisz nic, co robi, tylko wyniki, a potocznie mówiąc, że chcesz, aby proces był przezroczysty, oznacza, że ​​chcesz widzieć każdą jego część.
coladict
44
@coladict Spróbuj spojrzeć na to z innej perspektywy: nie jest to przezroczysty przypadek, w którym można zobaczyć wewnętrzne mechanizmy działania, ale cała sama funkcja jest dla ciebie przezroczysta, tzn. niewidoczna dla ciebie - wciąż wykonując swoją pracę i pozwalając ci zobaczyć to, co „ współpracuję z. W tym przypadku „płaski” oznacza przeciwieństwo „zagnieżdżonego”, flatmap usuwa jeden poziom zagnieżdżenia poprzez spłaszczenie.
Zefiro
7
@coladict „Przezroczysta” rzecz od lat zjada moją głowę. Cieszę się, że przynajmniej jedna inna osoba czuje to samo.
Ashok Bijoy Debnath
9
Spłaszczanie polega na przekształceniu struktury 2-poziomowej w strukturę jednopoziomową, patrz odpowiedź Dici na przykład stackoverflow.com/a/26684582/6012102
andrzej.szmukala
26
To najlepsze wytłumaczenie flatMap . Właśnie dlatego kliknięcie: wartości ze strumienia zwróconego przez program odwzorowujący są usuwane ze strumienia i przekazywane do strumienia wyjściowego. „Gromady” wartości zwracane przez każde wywołanie funkcji odwzorowującej wcale nie są rozróżniane w strumieniu wyjściowym, dlatego mówi się, że dane wyjściowe zostały „spłaszczone” . Dziękuję Ci!
neevek
464

Stream.flatMap, jak można się domyślić na podstawie nazwy, jest kombinacją operacji mapi flatoperacji. Oznacza to, że najpierw zastosujesz funkcję do swoich elementów, a następnie spłaszczysz ją. Stream.mapstosuje tylko funkcję do strumienia bez spłaszczania strumienia.

Aby zrozumieć, na czym polega spłaszczenie strumienia, rozważ taką strukturę, [ [1,2,3],[4,5,6],[7,8,9] ]która ma „dwa poziomy”. Spłaszczenie oznacza to przekształcenie go w „jeden poziom” strukturze: [ 1,2,3,4,5,6,7,8,9 ].

Dici
źródło
5
proste i słodkie
bluelurker
3
Haha, żeby być uczciwym, nadal jestem zaskoczony, widząc, jak duży ruch to pytanie. Kolejną zabawną obserwacją jest to, że minęło prawie 5 lat, kiedy napisałem tę odpowiedź, i istnieje dość spójny wzorzec głosowania, w którym zaakceptowana odpowiedź otrzymuje w przybliżeniu dwa głosy poparcia za każdą moją odpowiedź. Jest zaskakująco spójny.
Dici
1
jak to nie jest akceptowana odpowiedź, dziękuję za przejście od razu do sedna i postawienie bardzo prostego przykładu
1mike12
233

Chciałbym podać 2 przykłady, aby uzyskać bardziej praktyczny punkt widzenia:
Pierwszy przykład użycia mapy:

@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);
}   

W pierwszym przykładzie nie ma nic specjalnego, a Functionzwraca Stringwielką literą.

Drugi przykład wykorzystujący 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);
}

W drugim przykładzie przekazywany jest strumień listy. NIE jest to strumień liczb całkowitych!
Jeśli trzeba użyć funkcji transformacji (poprzez mapę), najpierw Strumień należy spłaszczyć na coś innego (Strumień liczb całkowitych).
Usunięcie flatMap powoduje zwrócenie następującego błędu: Operator + jest niezdefiniowany dla typów argumentów List, int.
NIE jest możliwe zastosowanie + 1 do Listy liczb całkowitych!

Rudy Vissers
źródło
@PrashanthDebbadwar Myślę, że skończyłbyś ze Strumieniem Stream<Integer>zamiast Strumienia Integer.
payne
166

Przejrzyj całkowicie post, aby uzyskać jasny pomysł,

mapa vs płaska mapa:

Aby zwrócić długość każdego słowa z listy, zrobilibyśmy coś takiego jak poniżej.

Krótka wersja podana poniżej

Kiedy zbieramy dwie listy, podane poniżej

Bez płaskiej mapy => [1,2], [1,1] => [[1,2], [1,1]] Tutaj dwie listy są umieszczone na liście, więc wynikiem będzie lista zawierająca listy

Z płaską mapą => [1,2], [1,1] => [1,2,1,1] Tutaj dwie listy są spłaszczone i tylko wartości są umieszczane na liście, więc wynik będzie listą zawierającą tylko elementy

Zasadniczo łączy wszystkie obiekty w jeden

## Szczegółowa wersja została podana poniżej: -

Na przykład: -
Zastanów się nad listą [„STOSOWANIE”, „OOOVVVER”], a my próbujemy zwrócić listę taką jak [„STACKOVER”] (zwracając tylko unikalne litery z tej listy) Początkowo robimy coś takiego jak poniżej, aby zwrócić lista [„STACKOVER”] z [„STACK”, „OOOVVVER”]

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());
  }
}

Problem polega na tym, że Lambda przekazana do metody map zwraca tablicę ciągów dla każdego słowa, więc strumień zwrócony przez metodę mapy jest w rzeczywistości typu Stream, ale potrzebujemy strumienia do reprezentowania strumienia znaków, poniżej obraz ilustruje problem.

Rycina A:

wprowadź opis zdjęcia tutaj

Możesz pomyśleć, że możemy rozwiązać ten problem za pomocą płaskiej mapy,
OK, pozwól nam zobaczyć, jak rozwiązać ten problem za pomocą map i Arrays.stream Przede wszystkim potrzebujesz strumienia znaków zamiast strumienia tablic. Istnieje metoda o nazwie Arrays.stream (), która pobierałaby tablicę i tworzy strumień, na przykład:

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());

Powyższe nadal nie działa, ponieważ teraz otrzymujemy listę strumieni (a dokładniej Stream>). Zamiast tego musimy najpierw przekonwertować każde słowo na tablicę pojedynczych liter, a następnie przekształcić każdą tablicę w osobny strumień

Korzystając z flatMap, powinniśmy być w stanie rozwiązać ten problem, jak poniżej:

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 wykonałby mapowanie każdej tablicy nie ze strumieniem, ale z zawartością tego strumienia. Wszystkie poszczególne strumienie, które zostałyby wygenerowane podczas korzystania z mapy (Arrays :: stream) zostaną scalone w jeden strumień. Rycina B ilustruje efekt zastosowania metody flatMap. Porównaj to z mapą na rysunku A. Rysunek B wprowadź opis zdjęcia tutaj

Metoda flatMap pozwala zastąpić każdą wartość strumienia innym strumieniem, a następnie łączy wszystkie wygenerowane strumienie w jeden strumień.

TechDog
źródło
2
Ładne wyjaśnienie schematyczne.
Hitesh
107

Odpowiedź w jednym wierszu: flatMappomaga spłaszczyć Collection<Collection<T>>aCollection<T> . W ten sam sposób spłaszczy się również Optional<Optional<T>>w Optional<T>.

wprowadź opis zdjęcia tutaj

Jak widać, map()tylko:

  • Typ pośredni to Stream<List<Item>>
  • Typ zwrotu to List<List<Item>>

oraz z flatMap():

  • Typ pośredni to Stream<Item>
  • Typ zwrotu to List<Item>

To wynik testu z kodu zastosowanego poniżej:

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

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

Zastosowany kod :

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);
  }
}
nxhoaf
źródło
8
Bardzo wyraźny przykład .., Nie zajmie to więcej niż kilka sekund, aby zrozumieć koncepcję z twoim przykładem ...
TechDog
2
ładne wyjaśnienie. naprawdę doceniam proste i najlepsze wytłumaczenie
Sachin Rane
42

Przekazywana funkcja stream.mapmusi zwrócić jeden obiekt. Oznacza to, że każdy obiekt w strumieniu wejściowym daje dokładnie jeden obiekt w strumieniu wyjściowym.

Przekazywana funkcja stream.flatMapzwraca strumień dla każdego obiektu. Oznacza to, że funkcja może zwrócić dowolną liczbę obiektów dla każdego obiektu wejściowego (w tym żaden). Powstałe strumienie są następnie łączone w jeden strumień wyjściowy.

Philipp
źródło
Dlaczego chcesz „zwrócić dowolną liczbę obiektów dla każdego obiektu wejściowego (w tym żaden)”?
Derek Mahar,
4
@DerekMahar Byłoby na to mnóstwo przypadków użycia. Załóżmy na przykład, że masz strumień Departments w swojej organizacji. Każdy dział ma od 0 do ns Employee. Potrzebny jest strumień wszystkich pracowników. Więc co robisz? Piszesz metodę flatMap, która przyjmuje dział i zwraca strumień jego pracowników.
Filip.
Philipp, czy twój przykład ilustruje główny powód użycia flatMap? Podejrzewam, że może to mieć charakter przypadkowy i nie ilustruje kluczowego przypadku użycia ani powodu, dla którego flatMapistnieje. (Ciąg dalszy poniżej ...)
Derek Mahar
Po przeczytaniu dzone.com/articles/understanding-flatmap , myślę, że główną motywacją flatMapjest dostosowanie się do błędów, które pojawiałyby się podczas korzystania map. Jak radzić sobie z przypadkami, w których jeden lub więcej elementów w oryginalnym zestawie nie można zmapować na element wyjściowy? Wprowadzając zestaw pośredni (powiedzmy Optionallub Stream) dla każdego obiektu wejściowego, flatMappozwala wykluczyć „nieprawidłowe” obiekty wejściowe (lub tak zwane „złe jabłka” w duchu stackoverflow.com/a/52248643/107158 ) z zestaw końcowy.
Derek Mahar,
1
@DerekMahar Tak, sytuacje, w których każdy obiekt wejściowy może, ale nie musi zwrócić obiekt wyjściowy, to kolejny dobry przypadek użycia dla płaskiej mapy.
Filip.
29

dla mapy mamy listę elementów i (funkcja, akcja) f, więc:

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

a dla płaskiej mapy mamy listę elementów i mamy (funkcję, akcję) fi chcemy, aby wynik został spłaszczony:

[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]
Bachiri Taoufiq Abderrahman
źródło
25

Mam wrażenie, że większość odpowiedzi tutaj komplikuje prosty problem. Jeśli już rozumiesz, jak mapdziała, to powinno być dość łatwe do zrozumienia.

Są przypadki, w których możemy skończyć z niechcianymi strukturami zagnieżdżonymi podczas używania map(), flatMap()metoda została zaprojektowana w celu przezwyciężenia tego przez unikanie zawijania.


Przykłady:

1

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

Możemy uniknąć zagnieżdżania list, używając 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));

gdzie:

private Optional<String> findById(Integer id)
Grzegorz Piwowarek
źródło
przepraszam, ale drugi fragment z punktu 1 nie jest kompilowany zamiast List<Integer> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(i -> i) .collect(Collectors.toList());. Powinno byćStream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(List::stream) .collect(Collectors.toList());
Arth Marur
@arthur Myślę, że użyłem tutaj strumienia i listy Vavr - ale zgadzam się, że to może być trochę mylące - zmienię to na standardową Javę
Grzegorz Piwowarek
@GrzegorzPiwowarek, co powiesz na to proste wyjaśnienie ?
Eugene
22

Artykuł Oracle na temat Opcjonalnego podkreśla różnicę między mapą a płaską mapą:

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

Niestety ten kod się nie kompiluje. Dlaczego? Komputer zmienny jest typu Optional<Computer>, więc wywołanie metody mapy jest całkowicie poprawne. Jednak getSoundcard () zwraca obiekt typu Optional. Oznacza to, że wynikiem operacji mapowania jest obiekt typu Optional<Optional<Soundcard>>. W rezultacie wywołanie getUSB () jest nieprawidłowe, ponieważ najbardziej zewnętrzna opcja zawiera jako swoją wartość inną opcję, która oczywiście nie obsługuje metody getUSB ().

W przypadku strumieni metoda flatMap przyjmuje funkcję jako argument, który zwraca inny strumień. Ta funkcja jest stosowana do każdego elementu strumienia, co spowoduje powstanie strumienia strumieni. Jednak flatMap powoduje zastąpienie każdego wygenerowanego strumienia zawartością tego strumienia. Innymi słowy, wszystkie oddzielne strumienie generowane przez funkcję łączą się lub „spłaszczają” w jeden pojedynczy strumień. Chcemy tutaj coś podobnego, ale chcemy „spłaszczyć” Opcjonalny dwupoziomowy w jeden .

Opcjonalnie obsługuje również metodę flatMap. Jego celem jest zastosowanie funkcji transformacji do wartości Opcjonalnej (podobnie jak w przypadku operacji na mapie), a następnie spłaszczenie wynikowej Opcjonalnej dwupoziomowej w jedną .

Tak więc, aby nasz kod był poprawny, musimy przepisać go w następujący sposób, używając flatMap:

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

Pierwszy flatMap zapewnia, że Optional<Soundcard>zwracany jest zamiast an Optional<Optional<Soundcard>>, a drugi flatMap spełnia ten sam cel, aby zwrócić an Optional<USB>. Zauważ, że trzecie wywołanie musi być tylko mapą (), ponieważ getVersion () zwraca łańcuch zamiast obiektu opcjonalnego.

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

Zardzewiały rdzeń
źródło
1
pytanie dotyczyło Stream.map i Stream.flatMap, a nie Optional.map anfd Optional.flatMap
djames
4
Ale bardzo pomogło mi zrozumieć moje problemy z opcjonalną i płaską mapą, wielkie dzięki!
Loïc
2
@djames, jest to całkowicie poprawna odpowiedź, przeczytaj ją zaczynając od akapitu „W przypadku strumieni metoda flatMap przyjmuje funkcję jako argument ...” :)
skwisgaar
Myślę, że jest to bardzo przydatny dodatek do niektórych innych odpowiedzi tutaj.
Mz A,
Wersja flatMap () generuje również wyjątek nullpoint, jeśli karta dźwiękowa ma wartość null. Więc gdzie jest obiecana korzyść z Opcjonalnego?
Ekaterina,
16

Nie jestem pewien, czy mam na to odpowiedzieć, ale za każdym razem, gdy spotykam się z kimś, kto tego nie rozumie, używam tego samego przykładu.

Wyobraź sobie, że masz jabłko. A mapprzekształca to jabłko apple-juicena przykład lub mapowanie jeden na jeden .

Weź to samo jabłko i wyciągnij z niego tylko nasiona, czyli to, co flatMaprobi, albo jedno do wielu , jedno jabłko jako wkład, wiele nasion jako wynik.

Eugene
źródło
4
To ciekawy przykład :)
kasjolina
W takim flatMapprzypadku, czy najpierw zbierasz nasiona z każdego jabłka w osobnych torebkach, po jednej torebce na jabłko, zanim wlejesz wszystkie torebki do jednej torebki?
Derek Mahar,
@DerekMahar to był biedny w jednej torbie przed java-10, co oznacza, że flatmapnie był zbyt leniwy, ale od java-10 jest leniwy
Eugene
@Eugene, proszę wyjaśnij nieco bardziej leniwą koncepcję, którą próbujesz mi wyjaśnić, nie jest dla mnie jasne. Zrozumiałem, co derkerMahar wyjaśnił w komentarzu, czy to, co dzieje się przed java10?
JAVA,
@JAVA po prostu wyszukaj flatMap + lazy, założę się, że będzie kilka odpowiedzi.
Eugene
16

map () i flatMap ()

  1. map()

Po prostu przyjmuje funkcję param lambda, w której T jest elementem, a R element powrotu zbudowany za pomocą T. Na końcu będziemy mieli strumień z obiektami typu R. Prostym przykładem może być:

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

Po prostu bierze elementy od 1 do 5 typu Integer, używa każdego elementu do zbudowania nowego elementu z typu Stringo wartości "prefix_"+integer_valuei drukuje go.

  1. flatMap()

Warto wiedzieć, że flatMap () przyjmuje funkcję F<T, R>gdzie

  • T jest rodzajem, z którego można budować strumień z / z . Może to być lista (T.stream ()), tablica (Arrays.stream (someArray)) itp. Cokolwiek, z czego strumień może być w / lub w formie. w poniższym przykładzie każdy deweloper ma wiele języków, więc dev. Języki to lista i będzie używać parametru lambda.

  • R jest powstałym Strumieniem, który zostanie zbudowany przy użyciu T. Wiedząc, że mamy wiele wystąpień T, naturalnie będziemy mieli wiele Strumieni od R. Wszystkie te Strumienie Typu R zostaną teraz połączone w jeden pojedynczy „płaski” Strumień od Typu R .

Przykład

Przykłady Bachiri Taoufiq, które widzą tutaj swoją odpowiedź, są proste i łatwe do zrozumienia. Dla jasności, powiedzmy, że mamy zespół programistów:

dev_team = {dev_1,dev_2,dev_3}

, przy czym każdy programista zna wiele języków:

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

Zastosowanie Stream.map () na dev_team, aby uzyskać języki każdego dewelopera:

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

da ci tę strukturę:

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

co jest w zasadzie a List<List<Languages>> /Object[Languages[]]. Nie tak bardzo ładnie, ani Java8 !!

dzięki Stream.flatMap()czemu możesz „spłaszczyć” rzeczy, ponieważ bierze powyższą strukturę
i przekształca ją {lang_a, lang_b, lang_c, lang_d, lang_e, lang_f}, co można zasadniczo wykorzystać jako List<Languages>/Language[]/etc...

więc w końcu twój kod miałby więcej sensu:

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();

lub po prostu:

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

Kiedy używać map () i flatMap () :

  • Użyj, map()gdy każdy element typu T ze strumienia ma być odwzorowany / przekształcony w pojedynczy element typu R. Wynikiem jest mapowanie typu (1 element początkowy -> 1 element końcowy) i nowy strumień elementów typu R jest zwracany.

  • Użyj, flatMap()gdy każdy element typu T ze strumienia ma zostać zmapowany / przekształcony w kolekcję elementów typu R. Wynikiem jest mapowanie typu (1 element początkowy -> n elementów końcowych) . Kolekcje te są następnie łączone (lub spłaszczane ) z nowym strumieniem elementów typu R. Jest to przydatne na przykład do reprezentowania zagnieżdżonych pętli .

Przed 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()
    .flatMap(foo -> foo.getMyBars().stream())
    .forEach(bar -> System.out.println(bar.getMyName()));
Arthur
źródło
11

Mapa: - Ta metoda przyjmuje jedną funkcję jako argument i zwraca nowy strumień składający się z wyników wygenerowanych przez zastosowanie przekazanej funkcji do wszystkich elementów strumienia.

Wyobraźmy sobie, że mam listę wartości całkowitych (1,2,3,4,5) i jeden interfejs funkcji, którego logika jest kwadratem podanej liczby całkowitej. (e -> e * e).

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);

wynik:-

[1, 4, 9, 16, 25]

Jak widać, wyjściem jest nowy strumień, którego wartości są kwadratem wartości strumienia wejściowego.

[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: - Ta metoda przyjmuje jedną funkcję jako argument, ta funkcja przyjmuje jeden parametr T jako argument wejściowy i zwraca jeden strumień parametru R jako wartość zwracaną. Zastosowanie tej funkcji do każdego elementu tego strumienia powoduje wygenerowanie strumienia nowych wartości. Wszystkie elementy tych nowych strumieni generowanych przez każdy element są następnie kopiowane do nowego strumienia, który będzie zwracaną wartością tej metody.

Wyobraźmy sobie, że mam listę przedmiotów studenckich, gdzie każdy uczeń może wybrać wiele przedmiotów.

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);

wynik:-

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

Jak widać, wyjściem jest nowy strumień, którego wartości są zbiorem wszystkich elementów strumieni zwracanych przez każdy element strumienia wejściowego.

[S1, S2, S3] -> [{„historia”, „matematyka”, „geografia”}, {„ekonomia”, „biologia”}, {„nauka”, „matematyka”}] -> podejmuj unikalne tematy - > [ekonomia, biologia, geografia, nauka, historia, matematyka]

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

lalitbhagtani
źródło
może coś zmienić, jeśli podasz kod zamiast tylko dowodu, że link do dokumentu
Charles-Antoine Fournel
11

.map służy do mapowania A -> B.

Stream.of("dog", "cat")              // stream of 2 Strings
    .map(s -> s.length())            // stream of 2 Integers: [3, 3]

konwertuje dowolny element Ana dowolny element B. Javadoc


.flatMap jest A -> Stream <B> concatinating

Stream.of("dog", "cat")             // stream of 2 Strings
    .flatMapToInt(s -> s.chars())   // stream of 6 ints:      [d, o, g, c, a, t]

to --1 przekształca dowolny element Aw Stream< B>, a następnie --2 łączy wszystkie strumienie w jeden (płaski) strumień. Javadoc


Uwaga 1: Chociaż ten ostatni przykład jest dopasowany do strumienia prymitywów (IntStream) zamiast strumienia obiektów (Stream), nadal ilustruje ideę .flatMap.

Uwaga 2: Pomimo nazwy metoda String.chars () zwraca wartości ints. Tak więc rzeczywista kolekcja będzie:, [100, 111, 103, 99, 97, 116] gdzie 100jest kod 'd', 111jest kodem 'o'itd. Ponownie, dla celów ilustracyjnych, jest prezentowany jako [d, o, g, c, a, t].

epoksyd
źródło
3
Najlepsza odpowiedź. Prosto do
rzeczy
1

Prosta odpowiedź.

mapOperację można produkować Streamz StreamExStream<Stream<Integer>>

flatMapoperacja przyniesie tylko Streamcoś. DAWNYStream<Integer>

Melad Basilius
źródło
0

Również dobra analogia może być z C #, jeśli znasz. Zasadniczo C # Selectpodobny do java mapi C # SelectManyjava flatMap. To samo dotyczy kolekcji Kotlin.

Arseniusz
źródło
0

Jest to bardzo mylące dla początkujących. Podstawowa różnica polega na tym, że mapemituje jeden element dla każdego wpisu na liście i flatMapjest w zasadzie operacją map+ flatten. Aby być bardziej zrozumiałym, użyj flatMap, gdy potrzebujesz więcej niż jednej wartości, np. Gdy oczekujesz, że pętla zwróci tablice, flatMap będzie naprawdę pomocny w tym przypadku.

Napisałem o tym bloga, możesz to sprawdzić tutaj .

Niraj Chauhan
źródło
0

Przesyłaj strumieniowo operacje flatMapi mapakceptuj funkcję jako dane wejściowe.

flatMapoczekuje, że funkcja zwróci nowy strumień dla każdego elementu strumienia i zwróci strumień, który łączy wszystkie elementy strumieni zwróconych przez funkcję dla każdego elementu. Innymi słowy, flatMapdla każdego elementu ze źródła wiele funkcji zostanie utworzonych przez funkcję. http://www.zoftino.com/java-stream-examples#flatmap-operation

mapoczekuje, że funkcja zwróci przekształconą wartość i zwróci nowy strumień zawierający przekształcone elementy. Innymi słowy, mapdla każdego elementu ze źródła jeden przekształcony element zostanie utworzony przez funkcję. http://www.zoftino.com/java-stream-examples#map-operation

Arnav Rao
źródło
0

flatMap()wykorzystuje również częściową leniwą ocenę strumieni. Odczyta pierwszy strumień i tylko w razie potrzeby przejdzie do następnego strumienia. Zachowanie jest szczegółowo wyjaśnione tutaj: czy flatMap gwarantuje lenistwo?

SK
źródło
0

Jeśli myślisz map()jako iteracja ( forpętla jednopoziomowa ), flatmap()to iteracja dwupoziomowa (jak forpętla zagnieżdżona ). (Wprowadź każdy iterowany element fooi powtórz foo.getBarList()w nim iterację barList)


map(): weź strumień, zrób coś z każdym elementem, zbierz pojedynczy wynik każdego procesu, wyślij kolejny strumień. Definicja „zrób coś funkcji” jest niejawna. Jeśli powstaje przetwarzanie dowolnego elementu null, nullsłuży do komponowania końcowego strumienia. Tak więc liczba elementów w strumieniu wynikowym będzie równa liczbie strumienia wejściowego.

flatmap(): weź strumień elementów / strumieni i funkcję (wyraźna definicja), zastosuj funkcję do każdego elementu każdego strumienia i zbierz cały wynikowy strumień pośredni, aby był większym strumieniem („spłaszczanie”). Jeśli wynikiem jest przetwarzanie dowolnego elementu null, pusty strumień jest dostarczany do ostatniego etapu „spłaszczania”. Liczba elementów w wynikowym strumieniu jest sumą wszystkich uczestniczących elementów we wszystkich danych wejściowych, jeśli dane wejściowe to kilka strumieni.

WesternGun
źródło