web-dev-qa-db-ja.com

RxJavaでmap vs flatMapをいつ使用しますか?

RxJavaでmap vs flatMapをいつ使用しますか?

たとえば、JSONを含むファイルをJSONを含む文字列にマッピングするとします。

Mapを使って、どういうわけか例外を処理しなければなりません。しかし、どうですか?

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // So Exception. What to do ?
        }
        return null; // Not good :(
    }
});

FlatMapを使用すると、はるかに冗長になりますが、問題をObservablesの連鎖に沿って転送し、他の場所を選択して再試行することでエラーを処理できます。

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override public void call(Subscriber<? super String> subscriber) {
                try {
                    String json = new Gson().toJson(new FileReader(file), Object.class);

                    subscriber.onNext(json);
                    subscriber.onCompleted();
                } catch (FileNotFoundException e) {
                    subscriber.onError(e);
                }
            }
        });
    }
});

私はmapの単純さが好きですが、flatmapのエラー処理(冗長ではありません)が好きです。私はこれについてのベストプラクティスが浮遊しているのを見たことがなく、これが実際にどのように使用されているのか興味があります。

165

mapは、あるイベントを別のイベントに変換します。 flatMapは、1つのイベントを0個以上のイベントに変換します。 (これは IntroToRx から取られます)

あなたのJSONをオブジェクトに変換したいので、mapを使うことで十分であるべきです。

FileNotFoundExceptionを処理することは別の問題です(mapまたはflatmapを使用してもこの問題は解決されません)。

あなたの例外問題を解決するには、単にチェックされていない例外を投げてください:RXはあなたのためにonErrorハンドラを呼び出します。

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // this exception is a part of rx-Java
            throw OnErrorThrowable.addValueAsLastCause(e, file);
        }
    }
});

flatmapとまったく同じバージョン:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            // this static method is a part of rx-Java. It will return an exception which is associated to the value.
            throw OnErrorThrowable.addValueAsLastCause(e, file);
            // alternatively, you can return Obersable.empty(); instead of throwing exception
        }
    }
});

FlatMapバージョンでは新しいObservableを返すこともできますが、これは単なるエラーです。

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
        }
    }
});
114
dwursteisen

FlatMapはmapと非常によく似た振る舞いをしますが、違いはそれが適用する関数がそれ自体が観測可能値を返すということです。

実用的な意味では、Mapが適用する関数は連鎖された応答を変換するだけです(Observableを返さない)。 FlatMapが適用する関数がObservable<T>を返すのに対し、メソッド内で非同期呼び出しを行う予定がある場合はFlatMapをお勧めします。

概要:

  • MapはT型のオブジェクトを返します
  • FlatMapはObservableを返します。

明確な例がここに見られます: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-Java-sdk

Couchbase Java 2.Xクライアントは便利な方法で非同期呼び出しを提供するためにRxを使います。 Rxを使っているので、それはメソッドmapとFlatMapを持っています、それらのドキュメントの説明は一般的な概念を理解するのに役立つかもしれません。

エラーを処理するには、購読者のonErrorをオーバーライドします。

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

それはこの資料を見るのを助けるかもしれません: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

RXでエラーを管理する方法に関する優れた情報源は、次の場所にあります。 https://Gist.github.com/daschl/db9fcc9d2b932115b679

75
1vand1ng0

あなたの場合は1つの入力と1つの出力しかないのでmapが必要です。

mapが提供する関数は単純にアイテムを受け取り、さらに(一度だけ)下に放出されるアイテムを返します。

flatMapが提供する関数はアイテムを受け取り、 "Observable"を返します。つまり、新しい "Observable"の各アイテムはさらに下に向かって個別に発行されます。

コードがあなたのために物事を片付けるかもしれません:

Observable.just("item1").map( str -> {
    System.out.println("inside the map " + str);
    return str;
}).subscribe(System.out::println);

Observable.just("item2").flatMap( str -> {
    System.out.println("inside the flatMap " + str);
    return Observable.just(str + "+", str + "++" , str + "+++");
}).subscribe(System.out::println);

出力:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
53
mt.uulu

map()の中に入れたい関数がflatMapを返すときにObservableを使うというのが私の考えです。その場合でもmap()を使おうとするかもしれませんが実用的ではないでしょう。その理由を説明してみましょう。

そのような場合にmapを使用することにした場合は、Observable<Observable<Something>>が表示されます。例えばあなたの場合、架空のRxGsonライブラリを使った場合、(単にStringを返すのではなく)toJson()メソッドからObservable<String>を返しています。

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}); // you get Observable<Observable<String>> here

現時点では、そのような観測可能なものに対してsubscribe()を実行するのはかなり難しいでしょう。その中にはObservable<String>がありますが、これを再度取得するにはsubscribe()が必要です。これは実用的ではないし、見てもいいです。

それでそれを有用にするために1つの考えは観察可能物のこの観察可能物を「平らにする」ことです(あなたは_flat_Mapという名前がどこから来るかを見始めるかもしれません)。 RxJavaは観測量を平坦化するためのいくつかの方法を提供しています、そして簡単のために merge が欲しいものだとしましょう。 Mergeは基本的に多数の観測量を取り、それらのうちのどれかが放出するときはいつでも放出します。 (たくさんの人が議論するでしょう switch がより良いデフォルトになるでしょう。しかし、あなたがただ一つの値を出しているのであれば、とにかく問題ではありません。)

それで、前のスニペットを修正すると、

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}).merge(); // you get Observable<String> here

これを購読する(またはマッピングする、フィルタリングする、または...)だけでStringという値が得られるため、これははるかに便利です。 (また、このようなmerge()の変種はRxJavaには存在しませんが、マージの概念を理解していれば、それがどのように機能するのかも理解していただければ幸いです。)

そのため、基本的にはこのようなmerge()はおそらくmap()が可観測値を返すことに成功したときにだけ役に立つはずであり、したがってこれを何度も入力する必要はないので、flatMap()は省略形として作成されました。通常のmap()と同じようにマッピング関数を適用しますが、後で返される値を出力する代わりに、それらを「平坦化」(またはマージ)します。

それが一般的なユースケースです。これは、場所を問わずRxを使用するコードベースで最も役立ちます。また、オブザーバブルを返す他のメソッドと連鎖させたいオブザーバブルを返すメソッドがたくさんあります。

map()onNext()で発行された1つの値をonNext()で発行された別の値にしか変換できないので、あなたのユースケースではそれは同様に便利です。しかし、それを複数の値に変換することはできません。まったく値がないか、エラーです。そして akarnokd が彼の答えに書いていたように(そしておそらく彼は私よりずっと賢いですが、少なくともRxJavaに関してはそう思いますが)あなたのmap()から例外を投げるべきではありません。その代わりにflatMap()

return Observable.just(value);

すべてうまくいったら

return Observable.error(exception);

何かが失敗したとき。
完全なスニペットについては彼の答えを参照してください。 https://stackoverflow.com/a/30330772/1402641

23

これが簡単なthumb-ruleです。これは、RxのObservableflatMap()の上にmap()を使うときを決めるのに役立ちます。

map変換を使用することにした場合、Objectを返すように変換コードを書くことになりますか。

変換の結果として返されるものが

  • 観測不可能なオブジェクトならmap()を使うことになります。そしてmap()はそのオブジェクトをObservableにラップしてそれを出力します。

  • Observableオブジェクト、それならflatMap()を使います。そしてflatMap()はObservableをアンラップし、返されたオブジェクトを選び、それを自身のObservableでラップしてそれを出力します。

たとえば、入力パラメータのTitled Cased Stringオブジェクトを返すtitleCase(String inputParam)というメソッドがあるとします。このメソッドの戻り型はStringまたはObservable<String>になります。

  • titleCase(..)の戻り値の型が単なるStringである場合は、map(s -> titleCase(s))を使用します。

  • titleCase(..)の戻り値の型がObservable<String>の場合、flatMap(s -> titleCase(s))を使用します。

それが明確になることを願っています。

16
karthiks

質問はいつRxJavaでmap vs flatMapを使用しますか?です。そして、私は簡単なデモがより具体的だと思います。

あなたが他の型に放出されたアイテムを変換したいとき、あなたのケースではファイルを文字列に変換することで、mapとflatMapは両方とも働くことができます。しかし、私は地図演算子が好きです。

しかし、ある場所ではflatMapは魔法の仕事をすることができますがmapはできません。たとえば、ユーザーの情報を取得したいのですが、ユーザーがログインするときに最初に彼のIDを取得する必要があります。明らかに2つのリクエストが必要で、それらは順番に並んでいます。

さぁ、始めよう。

Observable<LoginResponse> login(String email, String password);

Observable<UserInfo> fetchUserInfo(String userId);

二つのメソッドがあります。一つはloginがResponseを返したもので、もう一つはユーザー情報を取得するものです。

login(email, password)
        .flatMap(response ->
                fetchUserInfo(response.id))
        .subscribe(userInfo -> {
            // get user info and you update ui now
        });

お分かりのように、関数flatMapが適用されると、まずResponseからユーザーIDを取得し、それからユーザー情報を取得します。 2つのリクエストが終了したら、UIの更新やデータベースへのデータの保存などの仕事をすることができます。

しかしmapを使うと、そのようなNiceコードを書くことはできません。 Wordでは、flatMapがリクエストのシリアル化に役立ちます。

16
CoXier

私はflatMapでそれを追加したかっただけです、あなたは本当に関数の中であなた自身のカスタムObservableを使う必要はなく、あなたは標準的なファクトリメソッド/オペレータに頼ることができます:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        try {
            String json = new Gson().toJson(new FileReader(file), Object.class);
            return Observable.just(json);
        } catch (FileNotFoundException ex) {
            return Observable.<String>error(ex);
        }
    }
});

一般的に、RxJavaにできる限り多くの安全対策を講じているにもかかわらず、可能であればonXXXメソッドおよびコールバックからの(Runtime-)例外のスローを避けるべきです。

11
akarnokd

そのシナリオではmapを使って、新しいObservableを使う必要はありません。

exceptions.propagateを使用する必要があります。これはラッパーで、チェックされた例外をrxメカニズムに送信できます。

Observable<String> obs = Observable.from(jsonFile).map(new Func1<File, String>() { 
@Override public String call(File file) {
    try { 
        return new Gson().toJson(new FileReader(file), Object.class);
    } catch (FileNotFoundException e) {
        throw Exceptions.propagate(t); /will propagate it as error
    } 
} 
});

その後、サブスクライバでこのエラーを処理する必要があります。

obs.subscribe(new Subscriber<String>() {
    @Override 
    public void onNext(String s) { //valid result }

    @Override 
    public void onCompleted() { } 

    @Override 
    public void onError(Throwable e) { //e might be the FileNotFoundException you got }
};); 

それのための優秀な記事があります: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

6
ndori

場合によっては、一連のオブザーバブルを持つことになり、そのオブザーバブルから別のオブザーバブルが返されることがあります。 'flatmap'のようなものは、最初のものに埋め込まれている2番目のオブザーバブルをアンラップし、購読中に2番目のオブザーバブルが吐き出しているデータに直接アクセスできるようにします。

0
Anoop Isaac