はじめに
自分用のメモとしてですがRxJavaでつまづいたこと、よくあることなどを書きます
例として出してるコードは言いたいところ以外は省略します、あくまでイメージを掴むようと思ってください
そもそもRxJavaとは
リアクティブプログラミングをJavaで行うためのもの。
リアクティブプログラミングとは
先に処理だけ準備しておいてデータが来た時に準備しておいた処理を行うイメージ。
工場みたいに各工程の機械が設置されてて、製品が流れてきたタイミングで勝手に実行されるような。
処理を準備しているだけなので↓だけ書いても実行されない。
Single.just(1).doOnSuccess(System.out::println);
subscribe()することで初めてデータが流れてきて処理が実行される。
Single.just(1).doOnSuccess(System.out::println)
.subscribe();
// 1
よく使うものたち
大体使われるのは以下の4つ。
Single
1つのデータが入っている。0でもnでもなく1つ。
複数のデータもList型にすることで1つのListとして扱える。
Maybe
0 or 1つのデータが入ってる。Optionalみたいな感じ。
0の場合はdoOnComplete()、1の場合はdoOnSuccess()が実行される。
Single要素にfilterをかけるとMaybeになる。処理を分岐させるときに使ったりする。
map()、flatMap()などは0の場合は実行されない。
nullを入れるとエラー。
Observable
複数要素の集合。Streamみたいな感じ。
Maybe、Singleの集合でもある。
SingleをfilterするとMaybeになるが、ObservableにfilterをかけてもObservableのまま。
(集合からその要素が消えるだけ)
Flowable
ほぼObservableと同じ。大量のデータが流れてくる場合にはこっちを使う。
バックプレッシャー機能があるので、1億件のデータが流れてきたとしても100件ずつ取り出して
処理することでメモリがパンクしないように制御できる。
あるある
RxJava入門者がつまづきがちな話。
mapとflatMapなにが違うの
mapは1:1変換、flatMapは1:N変換。と言われてもイメージしづらいすよね。
map()
mapは流れてきたデータそのものを変換するだけ。
Single<X>のXの部分だけを変えるイメージ。
なので.map(i -> Single.just("a"))
ってするとSingle<Single<String>>になる。
Single.just(1) // Single<Integer>:1
.map(String::valueOf) // Single<String>:"1"
.doOnSuccess(System.out::println)
.subscribe();
// 1
Single.just(1) // Single<Integer>:1
.map(i -> List.of(1, 2, 3)) // Single<List<Integer>>:[1, 2, 3]
.doOnSuccess(System.out::println)
.subscribe();
// [1, 2, 3]
Observable.fromIterable(List.of(1, 2, 3)) // Observable<Integer>:[1, 2, 3]
.map(i -> List.of(i, i * 2)) // Observable<Integer>:[[1, 2], [2, 4], [3, 6]]
.doOnNext(System.out::println)
.subscribe();
// [1, 2]
// [2, 4]
// [3, 6]
flatMap()
変換した値を新たに集合に加えるイメージ。
Singleだと戻す値がSingleでネストされてしまう場合のSingleを取り外すぐらいにしか使わないかも。
Single.just(1) // Single<Integer>:1
.flatMap(i -> Single.just(i * 2)) // Single<Integer>:2
.doOnSuccess(System.out::println)
.subscribe();
// 2
Observable.fromIterable(List.of(1, 2, 3)) // Observable<Integer>:[1, 2, 3]
.flatMap(i -> Observable.fromIterable(List.of(i, i * 2))) // Observable<Integer>:[1, 2, 2, 4, 3, 6]
.doOnNext(System.out::println)
.subscribe();
// 1
// 2
// 2
// 4
// 3
// 6
処理が動かない
subscribe漏れ
上でも書きましたが処理を準備しているだけでは実行までされません、subscribeしましょう
Single.just(1).doOnSuccess(System.out::println)
.subscribe();
処理が流れに乗っていない
これもsubscribe漏れと同じといえば同じなんですがわりとやりがちです
private Single<String> func() {
return Single.just("a").doOnSuccess(System.out::println);
}
Single.just(1)
.map(i -> {
func(); // ここがsubscribeされない
return i;
})
.subscribe();
上記処理のmap内で構築してるSingleはsubscribeされていないので実行されません
チェーンがつながっていればまとめてsubscribeされるので、実行させたいなら↓のように書きましょう
private Single<String> func() {
return Single.just("a").doOnSuccess(System.out::println);
}
Single.just(1)
.flatMap(i -> func())
.subscribe();
// a
ちなみにfunc()内でもsubscribeすればどちらも実行されますが、その場合は2つのsubscribeが別々のスレッドで実行されるので意図しない挙動となる可能性の考慮が必要です
private Single<String> func() {
return Single.just("a").doOnSuccess(System.out::println)
.subscribe();
}
Single<Single>になってしまってる
これも結局subscribeされてないってことなんですが
Single.just(1)
.map(i -> Single.just("a").doOnSuccess(System.out::println)) // Single<Single<String>>
.subscribe();
map()のところでも書きましたがmap内でSingle型を返すとSingle<Single>>となってしまうので、内側のSingleがsubscribeされずに処理が実行されません
処理を実行させたければflatMapを使ってSingleがネストしないようにしましょう
Single.just(1)
.flatMap(i -> Single.just("a").doOnSuccess(System.out::println)) // Single<<String>
.subscribe();
// a
Observable → Listで順番が変わる
Observableは各要素に対してあらかじめ準備した処理を並列実行するため順序が保証されません
なのでtoList()を使用してList化すると実行するたびに順序が変化する可能性があります
Observable.fromIterable(List.of(1, 2, 3)) // Observable<Integer>:[1, 2, 3]
.map(i -> i * 2) // Observable<Integer>:[2, 4, 6]
.toList() // Single<List<Integer>>:[2, 4, 6]? [4, 6, 2]? [6, 4, 2]?
.doOnSuccess(System.out::println)
.subscribe();
順序を保証したい場合はtoSortedList()を使ってソート順を指定しましょう
Observable.fromIterable(List.of(1, 2, 3)) // Observable<Integer>:[1, 2, 3]
.map(i -> i * 2) // Observable<Integer>:[2, 4, 6]
.toSortedList(Integer::compareTo) // Single<List<Integer>>:[2, 4, 6]
.doOnSuccess(System.out::println)
.subscribe();
おわり
自分用メモですが誰かの理解の参考になれば。
他にも思いついたら追記していきます。