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

visible true

技術的なメモを書く

RxJavaのObservable<T>でOptional<T>を代行する

RxAndroidとRetrolambdaで大体Java8をAndroidに持ち込むでOptionalは使えず、良さ気なライブラリは見当たらないと書いたのだけど、そもそもRxJavaのObservable<T>Optional<T>と同等の機能を持ってるっぽくない?という事に気がついて試してみたらOptional<T>でした。とは言え若干違う点や面倒な点もあるので完全に置き換えられるって感じでもない気がしますが十分な気もします。そもそもAndroidがさっさとJava8をサポートしてくれたらいいんです。

参考

Optional.of()とOptional.ofNullable()を実装する

RxJavaの方にはOptional.of()とOptional.ofNullable()に対応するメソッドが無いのでこれは実装しておく必要があります。とはいえとてもシンプルです。実装にはObservable.just(T)Observable.empty()を使います。これでオブジェクトをObservableの世界に持ち上げる事ができます。

public class Optional {
  public static <T> Observable<T> of(T data){
    if(data == null){
      throw new NullPointerException();
    }
    else{
      return Observable.just(data);
    }
  }
  public static <T> Observable<T> ofNullable(T data){
    if(data == null){
      return Observable.empty();
    }
    else{
      return Observable.just(data);
    }
  }
}

ifPresent(), map(), filter(), flatMap()

Observable<T>にはifPresent(), map(), filter(), flatMap()に対応するメソッドがあります。それぞれ見ていきます。

ifPresent(Consumer<? super T> consumer)

ifPresent(Consumer<? super T> consumer)Observable.subscribe(Action1<? super T> onNext)で置き換えできます。

Observable<String> valueOpt = Optional.ofNullable("1,2,3");
valueOpt.subscribe(value -> {
      //呼び出される value="1,2,3"
    });

nullでも大丈夫。

Observable<String> valueOpt = Optional.ofNullable(null);
valueOpt.subscribe(value -> {
  //呼び出されない
});

map()

そのまんま。valueOptの中身がnullならmap()は実行されません。

Observable<String> valueOpt = Optional.ofNullable("1,2,3");
Observable<Integer> length =
  valueOpt.map(value -> value.split(",").length);

filter()

こちらもそのまんまです。

Observable<String> valueOpt = Optional.ofNullable("java");

//emptyになる
Observable<String> filtered =
  valueOpt.filter(value->value.startsWith("ruby"));

flatMap()

そのまんま!長くなるので解説はしません。

public Observable<String> addSuffix(String suffix, Observable<String> valueOpt){
  return valueOpt.flatMap(value->
    Optional.ofNullable(value + suffix));
}

get(), orElse()

次にget()orElse()を実装します。Observableから値を取り出すにはtoBlocking()を呼び出す必要があって冗長になるのでラップした方がいいでしょう。以下のようにOptianalにstaticメソッドで生やしておきます。

public static <T> T get(Observable<T> observable){
  return observable.toBlocking().single();
}
public static <T> T orElse(Observable<T> observable, T defaultValue){
  return observable.defaultIfEmpty(defaultValue).toBlocking().single();
}

これで値をObservableから取り出せます。

Observable<String> valueOpt = Optional.ofNullable("aaaa");
String value = Optional.get(valueOpt);
//value = "aaaa"

get()にemptyなObservableを渡すとNoSuchElementExceptionがthrowされます。

Observable<String> valueOpt = Optional.ofNullable(null);
String value = Optional.get(valueOpt);
//throw NoSuchElementException

デフォルト値が欲しい場合はorElse()を使えばOKです。

Observable<String> valueOpt = Optional.ofNullable("aaaa");
String value = Optional.orElse(valueOpt, "default");
//value = "aaaa"

nullでも安心。

Observable<String> valueOpt = Optional.ofNullable(null);
String value = Optional.orElse(valueOpt, "default");
//value = "default"

isPresent()

isPresent()も別途実装しておいた方がいいでしょう。

public static <T> boolean isPresent(Observable<T> observable){
  return observable.isEmpty().toBlocking().single();
}

orElseGet(), orElseThrow()

orElseGet(Supplier<? extends T> other)orElseThrow(Supplier<? extends X> exceptionSupplier) も簡単に用意できます。

public static <T> T orElseGet(Observable<T> observable, Func0<T> other) {
  return isPresent(observable) ? get(observable) : other.call();
}

public static <T, X extends Throwable> T orElseThrow(Observable<T> observable, Func0<? extends X> other)
throws X {
  if (isPresent(observable)) {
    return get(observable);
  } else {
    throw other.call();
  }
}

まとめ

ちょっとした実装は必要ですがOptionalの機能を満たす事が出来たのではないでしょうか。Stream APIとRxJavaでは使い心地が異なる点が結構あるのでやっぱりAndroidにJava8を完全に持ち込むには至らないですが十分強力だと思います。あとAndroidは早くJava8をサポートしてほしいです。