読者です 読者をやめる 読者になる 読者になる

visible true

技術的なメモを書く

ObservableがNoSuchElementExceptionで死ぬ時はscan(Func2)やreduce(Func2)にinitialValueを渡す

RxJava

問題

以下のコード(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を指定するとかいった使い方になるんじゃないかと思う。