ObservableがNoSuchElementExceptionで死ぬ時はscan(Func2)やreduce(Func2)にinitialValueを渡す
問題
以下のコード(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を指定するとかいった使い方になるんじゃないかと思う。