Lightweight Stream API を Android で使う

  • 10
    いいね
  • 0
    コメント

はじめに

Android 7.0 Developer Preview 2 から StreamOptional といった Java 8 で搭載された API が使えるようになりました。しかし、これは minSdkVersion を 24 にした場合だけです。しばらくはバックポートライブラリである Lightweight Stream API を使うと良いのではないでしょうか?

Lightweight Stream API はバックポートライブラリですが、本家の StreamOptional の完全なポーティングライブラリではありません。 Lightweight と付いているとおり一部の API のみが実装されています。しかし、非常に魅力的なライブラリです。また、 Android で StreamOptional を使うと何が嬉しいのかを考えてみたいと思います。

Stream

Stream はコレクションの操作に役立ちます。 filtermap オペレーターを挟みながら、流れるように値を加工・変換して最終的にほしい結果を得ることができます。 ListViewRecyclerView など、コレクションを表示する API と組み合わせると良いのではないでしょうか?

クラシック
String[] names = {"hello", "world"};
List<String> upperNames = new ArrayList<>(names.size());
for (String name : names) {
    upperNames.add(name.toUpperCase());
}

このようなコードが、

Stream
String[] names = {"hello", "world"};
List<String> upperNames = Stream.of(names)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

こう書けます。これだけだと行数も対した違いはありませんが、特定の条件にマッチする値だけを格納したコレクションを作りたいなど、条件が複雑になってくると、素直に書くとネストが深くなっていきます。

クラシック
String[] names = {"hello", "world"};
List<String> upperNames = new ArrayList<>(names.size());
for (String name : names) {
    if (name.equals("hello")) {
        upperNames.add(name.toUpperCase());
    }
}

しかし、 Stream を使うことでシンプルに記述することができます。

Stream
String[] names = {"hello", "world"};
List<String> upperNames = Stream.of(names)
    .filter(name -> name.equals("hello"))
    .map(String::toUpperCase)
    .collect(Collectors.toList()); // 他にも toSet や toMap メソッドが用意されている

name -> name.equals("hello")String::toUpperCase に関しては Java 8 の Lambda 式です。 StreamOptional も Lambda 式がないと冗長な記述が必要になってしまうため、 Android で使うときは Jack コンパイラRetrolambda を使うことをオススメします。

RxJava を使っている場合、

RxJava
String[] names = {"hello", "world"};
List<String> upperNames = Observable.from(names)
    .filter(name -> name.equals("hello"))
    .map(String::toUpperCase)
    .toList()
    .toBlocking()
    .single();

BlockingObservable に変換することで Stream と同じように同期的に変換をかけることができましたが、 Stream を使うことでより短く、シンプルに記述することができます。

Optional

Optional は Android 開発でジワジワと効いてくる API です。 Android フレームワークの API は値がない場合に null を返す API が多く、必然的に if-null チェックを入れなければならない場面が多いですが、 Optional を活用することで、記述をシンプルに、また、統一的なインターフェースを保つことができるようになります。

クラシック
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
    actionBar.setTitle("Hello, world");
}

例えばこんな例です。 AppCompatActivitygetSupportActionBar メソッドは値がない場合は null を返すメソッドです。コード自体に Nullable アノテーションが付いているため、取得した値にアクセスする際に if-null チェックを行わないと Android Studio が警告を出してきます。

Optional を使うと、例えばこんな風に書くことができます。

Optional
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

Optional.ofNullable(getSupportActionBar())
    .ifPresent(actionBar -> actionBar.setTitle("Hello, world"));

Nullable アノテーションが付いているメソッドを Optional.ofNullable でラップして、 ifPresent で値が存在する場合にだけ処理を続行させることができます。これだけだと単なる if-null チェックと変わらないですが、 Optional にも Stream と同じように mapfilter などのオペレーターがあり、さらに、 orElseorElseGet といった値がない場合の代替を明示するメソッドも用意されています。

例として、 FragmentManagerfindFragmentByTag メソッドを使って、タグづけられた Fragment がない場合は新しく生成する処理を Optional を用いて書いてみたいと思います。まずは素直に Java のコードで書いた場合です。

クラシック
Fragment fragment = getSupportFragmentManager().findFragmentByTag("sample");
SampleFragment sampleFragment;
if (fragment != null && fragment instanceof SampleFragment) {
    sampleFragment = (SampleFragment) fragment;
} else {
    sampleFragment = SampleFragment.newInstance();
}

このような形になるのではないでしょうか。次に Optional を用いる場合です。

Optional
SampleFragment sampleFragment = Optional
    .ofNullable(getSupportFragmentManager().findFragmentByTag("sample"))
    .filter(fragment -> fragment instanceof SampleFragment)
    .map(fragment -> (SampleFragment) fragment)
    .orElseGet(SampleFragment::newInstance);

どうでしょうか?とてもシンプルで明快なコードになったと思います。一度 findFragmentByTag メソッドで取得した Fragment を変数に格納する必要がありませんし、コード上も条件にマッチしたら取得し、マッチしない場合は新規作成するということが流れるように記述できました。

おわりに

Lightweight Stream API を使うと、 Java 8 の StreamOptional を Android に持ち込むことができます。さらに Jack コンパイラや Retrolambda を用いて Lambda 式が使うことで、記述を簡潔にすることができます。

Stream はコレクション操作に役立ち、 Optional は Android フレームワークにありがちな null を返すメソッドをラップすることでコードの記述を明快にすることができます。個人的には Fragment の処理をシンプルにできるだけでも導入の価値があると考えています。

おまけ - 例えば Kotlin を使う

Kotlin を使うという手もありますね。言語自体が Null-Safety をうたっていて、 StreamOptional 的な機構は言語に備わっています。個人的には良い選択肢の 1 つだと思っています。既にあるプロジェクトに対してなら、まずは Lightweight Stream API や Retrolambda は導入しやすいと思うので、ここから初めて見ても良いと思います。