web-dev-qa-db-ja.com

Java 8のmapメソッドとflatMapメソッドの違いは何ですか?

Java 8では、 Stream.mapStream.flatMap methodsの違いは何ですか?

546
cassiomolin

mapflatMapはどちらもStream<T>に適用でき、どちらもStream<R>を返します。違いは、map操作は各入力値に対して1つの出力値を生成するのに対して、flatMap操作は各入力値に対して任意の数(ゼロ以上)の値を生成することです。

これは各操作の引数にも反映されています。

map操作はFunctionを取ります。これは入力ストリームの各値に対して呼び出され、1つの結果値を生成します。これは出力ストリームに送信されます。

flatMap操作は、概念的に1つの値を消費し、任意の数の値を生成することを望む関数を取ります。ただし、Javaでは、メソッドが返すことができる値は0個または1個のみなので、メソッドが任意の数の値を返すことは面倒です。 flatMapのマッパー関数が値を取り、値の配列またはListを返すAPIを想像することができます。そしてそれはそれから出力に送られます。これがストリームライブラリであることを考えると、任意の数の戻り値を表現するのに特に適した方法は、マッパー関数自体がストリームを返すことです。マッパーによって返されたストリームからの値はストリームから排出され、出力ストリームに渡されます。マッパー関数への各呼び出しによって返される値の「まとまり」は、出力ストリーム内でまったく区別されないため、出力は「平坦化された」と言われます。

典型的な使い方は、flatMapのマッパー関数がゼロ値を送りたい場合はStream.empty()を返し、複数の値を返したい場合はStream.of(a, b, c)のようなものです。しかし、もちろん任意のストリームを返すことができます。

660
Stuart Marks

Stream.flatMapは、その名前から推測できるように、map操作とflat操作の組み合わせです。つまり、最初に要素に関数を適用し、次にそれを平坦化するということです。 Stream.mapは、ストリームをフラット化せずにストリームに関数を適用するだけです。

flattening ストリームが何であるかを理解するために、 "2つのレベル"を持つ[ [1,2,3],[4,5,6],[7,8,9] ]のような構造を考えてください。これを平坦化するということは、それを「1レベル」構造に変換することを意味します:[ 1,2,3,4,5,6,7,8,9 ]

331
Dici

more の実用的な観点から、2つの例を挙げたいと思います。
mapを使った最初の例:

@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を使用する2番目の例:

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

2番目の例では、リストのストリームが渡されます。 それは整数の流れではない!
変換関数を(マップを通じて)使用する必要がある場合は、最初にStreamを別のもの(Flat of Integer)に平坦化する必要があります。
flatMapが削除されると、次のエラーが返されます。 演算子+は、引数の型List、intに対して未定義です。
整数のリストに+ 1を適用することはできません。

200
Rudy Vissers

明確なアイデアを得るために記事をよく読んでください。

map vs flatMap:

リストから各Wordの長さを返すには、以下のようにします。

下記のショートバージョン

下記の2つのリストを集めるとき

なしフラットマップ => [1,2]、[1,1] => [[1,2]、[1、 1]] ここでは2つのリストがリストの中に置かれているので、出力はリストを含むリストになります。

Withフラットマップ => [1,2]、[1,1] => [1,2,1,1] ここでは2つのリストが平坦化され、値だけがlistに配置されるので、出力は要素のみを含むリストになります。

基本的に全てのオブジェクトを一つに統合します

##詳細なバージョンは以下の通りです: -

例えば:-
リストを考えてみましょう ["STACK"、 "OOOVVVER"] そして私たちは ["STACKOVER"] のようなリストを返そうとしています。リストを返すには以下のようにします ["STACKOVER"] から ["" 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());
  }
}

ここでの問題は、mapメソッドに渡されたLambdaが各WordのString配列を返すことです。したがって、mapメソッドによって返されるストリームは実際にはStream型ですが、必要なのは文字ストリームを表すStreamです。問題。

図A:

enter image description here

フラットマップを使ってこの問題を解決することができます。
さて、mapArrays.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は各配列をstreamではなくそのストリームの内容でマッピングします。 map(Arrays :: stream)を使用している間に生成される個々のストリームはすべて、単一のストリームにマージされます。図Bは、flatMapメソッドを使用した効果を示しています。図Aでマップが何をしているかと比較してください。 図Benter image description here

flatMapメソッドを使用すると、ストリームの各値を別のストリームに置き換えてから、生成されたすべてのストリームを単一のストリームに結合できます。

115
TechDog

1行の回答:flatMapCollection<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);
  }
}
59
nxhoaf

stream.mapに渡す関数は1つのオブジェクトを返さなければなりません。つまり、入力ストリームの各オブジェクトは、出力ストリームのオブジェクトを1つだけにします。

stream.flatMapに渡した関数は、各オブジェクトのストリームを返します。つまり、この関数は各入力オブジェクトに対してオブジェクトをいくつでも返すことができます(なしを含む)。結果のストリームは、その後1つの出力ストリームに連結されます。

38
Philipp

mapの場合、要素のリストと(function、action)fがあります。

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

フラットマップの場合は、要素のリストlistと(function、action)fがあり、結果をフラット化する必要があります。

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

私はここでのほとんどの答えが単純な問題を過度に複雑にしているように感じます。 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

OracleのOptionalの記事では、mapとflatmapの違いを強調しています。

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

残念ながら、このコードはコンパイルされません。どうして?変数computerはOptional<Computer>型なので、mapメソッドを呼び出すことは完全に正しいです。ただし、getSoundcard()はOptional型のオブジェクトを返します。これは、マップ操作の結果がOptional<Optional<Soundcard>>型のオブジェクトであることを意味します。その結果、getUSB()の呼び出しは無効になります。最外部のOptionalには、値として別のOptionalが含まれているためです。もちろん、これはgetUSB()メソッドをサポートしません。

ストリームでは、flatMapメソッドは引数として関数を取り、それは別のストリームを返します。この関数はストリームの各要素に適用され、その結果ストリームのストリームが生成されます。ただし、flatMapには、生成された各ストリームをそのストリームの内容で置き換えるという効果があります。言い換えれば、この関数によって生成されるすべての別々のストリームは、1つの単一ストリームに統合または「フラット化」されます。ここで欲しいのは似たようなものですが、 2レベルのOptionalを1に平坦化したい です。

オプションでflatMapメソッドもサポートされています。その目的は、(map操作と同じように)オプションの値に変換関数を適用してから、 結果の2レベルのオプションを単一の にフラット化することです。

そのため、コードを正しくするには、flatMapを使用して次のように書き直す必要があります。

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

最初のflatMapはOptional<Soundcard>の代わりにOptional<Optional<Soundcard>>が返されることを保証し、2番目のflatMapはOptional<USB>を返すという同じ目的を達成します。 getVersion()はOptionalオブジェクトではなくStringを返すため、3番目の呼び出しは単にmap()である必要があります。

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

18
Rusty Core

私はこれに答えることになっているかどうかはよくわかりませんが、これを理解できない人に出会うたびに、同じ例を使用します。

あなたがAppleを持っていると想像してください。 mapはそのAppleを例えばApple-juiceまたは 一対一 マッピングに変換しています。

それと同じAppleを取り、そこから種だけを取り出してください。それがflatMapがすること、または 1対多 、入力として1台のApple、出力として多くの種です。

11
Eugene

map()およびflatMap()

  1. map()

Functionにラムダパラメータを指定します。ここで、Tは要素、RはTを使用して構築された戻り要素です。最後に、タイプRのオブジェクトを含むStreamがあります。簡単な例を次に示します。

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

タイプIntegerの要素1から5を受け取り、各要素を使用して、タイプStringから値"prefix_"+integer_valueで新しい要素を構築し、出力します。

  1. flatMap()

FlapMap()は関数F<T, R>をとることを知っていると便利です。

  • Tはからの型であり、Streamを/ withから構築できますリスト(T.stream())、配列(Arrays.stream(someArray))などを指定できます。Streamを使用して、またはフォームを作成できるものであれば何でもかまいません。以下の例では、各開発者は多くの言語を持っているため、開発者です。言語はリストであり、ラムダパラメータを使用します。

  • Rは、Tを使用して構築される結果のストリームです。Tのインスタンスが多数あることを知っているため、Rからのストリームは自然に多くなります。タイプRからのこれらのストリームはすべてに結合されますoneタイプRからの単一の「フラット」ストリーム.

Bachiri Taoufiq ここでの回答を参照 の例はシンプルで理解しやすいです。明確にするために、開発者のチームがあるとだけ言ってみましょう。

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}

Dev_teamでStream.map()を適用して、各devの言語を取得します。

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()を使用する場合

  • ストリームのタイプTの各要素がタイプRのsingle要素にマッピング/変換されることになっている場合は、map()を使用します。結果は、タイプ(1開始要素-> 1終了要素)およびタイプRの要素の新しいストリームが返されます。

  • ストリームのタイプTの各要素が、タイプRの要素のCollectionsにマッピング/変換されることになっている場合は、flatMap()を使用します。結果は、タイプ(1開始要素-> n終了要素)。これらのコレクションは、merged(またはflattened)タイプRの要素の新しいストリームにマージされます。これは、たとえばネストされたループ

プレ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()));
11
arthur

Map: - このメソッドは引数として1つの関数を取り、渡された関数をストリームのすべての要素に適用することによって生成された結果からなる新しいストリームを返します。

想像してみましょう、私は整数値(1,2,3,4,5)のリストと論理が渡された整数の2乗である1つの関数インターフェースを持っています。 (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);

出力:-

[1, 4, 9, 16, 25]

ご覧のとおり、出力は新しいストリームで、その値は入力ストリームの値の2乗です。

[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: - このメソッドは引数として1つの関数を取り、この関数は入力引数として1つのパラメータTを受け取り、戻り値としてパラメータRの1つのストリームを返します。この関数がこのストリームの各要素に適用されると、新しい値のストリームが生成されます。その後、各要素によって生成されたこれらの新しいストリームのすべての要素が新しいストリームにコピーされます。これがこのメソッドの戻り値になります。

イメージしてみましょう。私は生徒オブジェクトのリストを持っています。各生徒は複数の科目を選ぶことができます。

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はリスト内の各エントリに対して1つの項目を出力し、flatMapは基本的にmap + flatten操作です。より明確にするために、複数の値が必要なとき、たとえばループが配列を返すことを期待しているときにflatMapを使用してください。この場合、flatMapは本当に役に立ちます。

私はこれについてブログを書きました、あなたはそれをチェックアウトすることができます ここ

1
Niraj Chauhan

簡単な答えです。

map操作は、StreamStreamを生成することができますEX Stream<Stream<Integer>>

flatMap操作は何かのStreamを生成するだけです。 EX Stream<Integer>

1
Melad Ezzat

慣れ親しんでいるのであれば、C#でも同様のことが言えます。基本的にC#SelectはJava mapおよびC#SelectMany Java flatMapに似ています。コレクションについても同様です。

1
Arsenius

ストリーム操作flatMapmapは、入力として関数を受け取ります。

flatMapは、関数がストリームの各要素に対して新しいストリームを返すことを期待し、各要素に対して関数によって返されるストリームのすべての要素を組み合わせたストリームを返します。言い換えれば、flatMapでは、ソースからの各要素に対して、関数によって複数の要素が作成されます。 http://www.zoftino.com/Java-stream-examples#flatmap-operation

mapは、関数が変換された値を返すことを期待し、変換された要素を含む新しいストリームを返します。言い換えれば、mapでは、ソースからの各要素に対して、変換された要素が1つ関数によって作成されます。 http://www.zoftino.com/Java-stream-examples#map-operation

0
Arnav Rao

flatMap()はストリームの部分的な遅延評価も利用します。最初のストリームを読み込み、必要なときだけ次のストリームに進みます。動作はここで詳細に説明されます: flatMapは怠惰であることが保証されていますか?

0
S.K.