Lightweight Stream API を Android で使う

## はじめに

Android 7.0 Developer Preview 2 から `Stream` や `Optional` といった Java 8 で搭載された API が使えるようになりました。しかし、これは `minSdkVersion` を 24 にした場合だけです。しばらくはバックポートライブラリである [Lightweight Stream API](https://github.com/aNNiMON/Lightweight-Stream-API) を使うと良いのではないでしょうか？

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

## Stream

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

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

このようなコードが、

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

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

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

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

```java: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 式です。 `Stream` も `Optional` も Lambda 式がないと冗長な記述が必要になってしまうため、 Android で使うときは [Jack コンパイラ](https://source.android.com/source/jack.html)か [Retrolambda](https://github.com/evant/gradle-retrolambda) を使うことをオススメします。

RxJava を使っている場合、

```java: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` を活用することで、記述をシンプルに、また、統一的なインターフェースを保つことができるようになります。

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

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

例えばこんな例です。 `AppCompatActivity` の [`getSupportActionBar` メソッド](https://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html#getSupportActionBar())は値がない場合は `null` を返すメソッドです。コード自体に [`Nullable` アノテーションが付いている](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java#101)ため、取得した値にアクセスする際に `if-null` チェックを行わないと Android Studio が警告を出してきます。

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

```java: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` と同じように `map` や `filter` などのオペレーターがあり、さらに、 `orElse` や `orElseGet` といった値がない場合の代替を明示するメソッドも用意されています。

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

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

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

```java: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 の `Stream` と `Optional` を Android に持ち込むことができます。さらに Jack コンパイラや Retrolambda を用いて Lambda 式が使うことで、記述を簡潔にすることができます。

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

## おまけ - 例えば Kotlin を使う

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