visible true

技術的なメモを書く

Java 8: No more loopsをRxJavaでやる(Androidの環境で)

Java 8: No more loops

Java8のStream APIを使ってforループを無くすという記事。面白そうだったのでRxJava化しました。AndroidだとJava8 Stream API使えないですからね (´・ω・`)。Java8世界ではRxJavaってどういう位置づけなんでしょう気になります。

元記事と同じ様に、forループのあるコードとRxJavaでのコードを並べる形にします。Articleクラスについてはコチラには記載しないので元記事を見て下さい。シンプルなモデルなので見るまでもないかもしれません。

AndroidでのJava8環境構築は、

RxAndroidとRetrolambdaで大体Java8をAndroidに持ち込む - visible true を参照して下さい。

getFirstJavaArticle()

タグにJavaを含む最初のArticleを返す。

for loop

public Article getFirstJavaArticle() {
    for (Article article : articles) {
        if (article.getTags().contains("Java")) {
            return article;
        }
    }
    return null;
}

RxJava

AndroidなJava8ではOptionalがないのでObservableを返す形にしました。

public Observable<Article> getFirstJavaArticle() {
  return Observable.from(articles)
    .filter(articles -> articles.getTags().contains("Java"))
    .firstOrDefault(null);
}

使い方

即座に値を取り出すなら以下。

Article first = getFirstJavaArticle().toBlocking().single();
if (first != null) {
  //do something...
}

subscribeできるなら、

getFirstJavaArticle()
  .filter(articles -> articles != null)
  .subscribe(articles -> {
    //do something...
  });

getAllJavaArticles()

タグにJavaを含む全てのArticleを返す。

for loop

public List<Article> getAllJavaArticles() {
    List<Article> result = new ArrayList<>();
    for (Article article : articles) {
        if (article.getTags().contains("Java")) {
            result.add(article);
        }
    }
    return result;
}

RxJava

Stream APIと比べると呼び出しが多い。

public List<Article> getAllJavaArticles() {
  return Observable.from(articles)
    .filter(articles -> articles.getTags().contains("Java"))
    .toList()
    .toBlocking()
    .single();
}

groupByAuthor()

authorをkey、Article一覧をvalueとしたMapを返す。

for loop

public Map<String, List<Article>> groupByAuthor() {
    Map<String, List<Article>> result = new HashMap<>();
    for (Article article : articles) {
        if (result.containsKey(article.getAuthor())) {
            result.get(article.getAuthor()).add(article);
        } else {
            ArrayList<Article> articles = new ArrayList<>();
            articles.add(article);
            result.put(article.getAuthor(), articles);
        }
    }
    return result;
}

RxJava

RxJavaではCollectorsに代わるものはなさそうでちょっと汚い感じに。もっといい方法があるかも。toBlocking()collect()の中で呼んでるとsubscribedOn(), observeOn()のスレッド関係なくデッドロックする問題にはまりました。

public Map<String, List<Article>> groupByAuthor() {
  return Observable.from(articles)
    .groupBy(Article::getAuthor)
    .collect(new HashMap<String, List<Article>>(),
      (m, o) -> {
        o.toList().subscribe(list -> m.put(o.getKey(), list));
      })
    .toBlocking().single();
}

getDistinctTags()

タグのセットを返す。

for loop

public Set<String> getDistinctTags() {
    Set<String> result = new HashSet<>();
    for (Article article : articles) {
        result.addAll(article.getTags());
    }
    return result;
}

RxJava

こちらもちょっと冗長。HashSet<String>::newって渡せなかったのが悔やまれる。

public Set<String> getDistinctTags() {
  return Observable.from(articles)
    .flatMap(article -> Observable.from(article.getTags()))
    .distinct(tag -> tag)
    .toList()
    .map(list -> new HashSet<>(list))
    .toBlocking().single();
}

まとめ

  • Java8はサイコーである
  • RxJavaでも頑張ればStream API的な事が出来そう
  • No more loops
  • forループだけでなくifもなくなっている事がわかる
  • 各処理が分割されているのでやりたいことが明快になる
  • RxJava世界から値を取り出すのはちょっと冗長