問題
以下のコード(Retrolampda 1.8.0 + RxJava 1.0.0-rc.9で書いてます)では空のリストをObservable.from()
に渡している。これを単純にmap()
したりsubscribe()
するだけなら特に問題はでない。しかし、reduce(Func2)
やscan(Func2)
などが途中に挟まるとエラーになる。
List<String> amounts = new ArrayList<>(); Observable.from(amounts) .map(Integer::parseInt) .reduce((a, b) -> a + b) .subscribe( total -> { //do something. }, e -> { //error handling. });
エラーはNoSuchElementException
。要素が無いよ!という風に怒られて死ぬ。
java.util.NoSuchElementException: Sequence contains no elements
原因
上記のコードだとreduce(Func2)
が原因。RxJavaのreduce(Func2)
の実装を見ると以下の通り。
public final Observable<T> reduce(Func2<T, T, T> accumulator) { return scan(accumulator).last(); }
scan(Func2)
のあとlast()
を呼んでいる。last()
の中でtakeLast(1).single()
を呼んでいるが、single()
の所で要素が無いのでNoSuchElementException
が飛ぶ。
public final Observable<T> last() { return takeLast(1).single(); }
対応
対応はreduce(Func2)
の代わりにreduce(R initialValue, Func2)
を使う事。scan()の方も同様。これでObservable.from()
に渡すdata sourceが空でも問題が出ない。
List<String> amounts = new ArrayList<>(); Observable.from(amounts) .map(Integer::parseInt) .reduce(0, (a, b) -> a + b) .subscribe( total -> { //do something. }, e -> { //error handling. });
まとめ
NoSuchElementExceptionが出たらinitialValue。initialValueを使うかどうかは設計によると思う。空になり得ない場合は指定する意味はないので。例えばListViewのdata setを何らかの契機で集計処理にかけたいとかいった場合、data setが空の場合があるのでinitialValueを指定するとかいった使い方になるんじゃないかと思う。