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のエラー処理(冗長ではありません)が好きです。私はこれについてのベストプラクティスが浮遊しているのを見たことがなく、これが実際にどのように使用されているのか興味があります。
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));
}
}
});
FlatMapはmapと非常によく似た振る舞いをしますが、違いはそれが適用する関数がそれ自体が観測可能値を返すということです。
実用的な意味では、Mapが適用する関数は連鎖された応答を変換するだけです(Observableを返さない)。 FlatMapが適用する関数がObservable<T>
を返すのに対し、メソッド内で非同期呼び出しを行う予定がある場合はFlatMapをお勧めします。
概要:
明確な例がここに見られます: 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
あなたの場合は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+++
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
これが簡単なthumb-ruleです。これは、RxのObservable
でflatMap()
の上に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))
を使用します。
それが明確になることを願っています。
質問はいつ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
がリクエストのシリアル化に役立ちます。
私は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-)例外のスローを避けるべきです。
そのシナリオでは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/
場合によっては、一連のオブザーバブルを持つことになり、そのオブザーバブルから別のオブザーバブルが返されることがあります。 'flatmap'のようなものは、最初のものに埋め込まれている2番目のオブザーバブルをアンラップし、購読中に2番目のオブザーバブルが吐き出しているデータに直接アクセスできるようにします。