はじめに
Android 7.0 Developer Preview 2 から Stream
や Optional
といった Java 8 で搭載された API が使えるようになりました。しかし、これは minSdkVersion
を 24 にした場合だけです。しばらくはバックポートライブラリである Lightweight Stream API を使うと良いのではないでしょうか?
Lightweight Stream API はバックポートライブラリですが、本家の Stream
や Optional
の完全なポーティングライブラリではありません。 Lightweight と付いているとおり一部の API のみが実装されています。しかし、非常に魅力的なライブラリです。また、 Android で Stream
や Optional
を使うと何が嬉しいのかを考えてみたいと思います。
Stream
Stream
はコレクションの操作に役立ちます。 filter
や map
オペレーターを挟みながら、流れるように値を加工・変換して最終的にほしい結果を得ることができます。 ListView
や RecyclerView
など、コレクションを表示する API と組み合わせると良いのではないでしょうか?
String[] names = {"hello", "world"};
List<String> upperNames = new ArrayList<>(names.size());
for (String name : names) {
upperNames.add(name.toUpperCase());
}
このようなコードが、
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
を使うことでシンプルに記述することができます。
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 コンパイラか Retrolambda を使うことをオススメします。
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");
}
例えばこんな例です。 AppCompatActivity
の getSupportActionBar
メソッドは値がない場合は null
を返すメソッドです。コード自体に Nullable
アノテーションが付いているため、取得した値にアクセスする際に if-null
チェックを行わないと Android Studio が警告を出してきます。
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 のコードで書いた場合です。
Fragment fragment = getSupportFragmentManager().findFragmentByTag("sample");
SampleFragment sampleFragment;
if (fragment != null && fragment instanceof SampleFragment) {
sampleFragment = (SampleFragment) fragment;
} else {
sampleFragment = SampleFragment.newInstance();
}
このような形になるのではないでしょうか。次に 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 を使うという手もありますね。言語自体が Null-Safety をうたっていて、 Stream
や Optional
的な機構は言語に備わっています。個人的には良い選択肢の 1 つだと思っています。既にあるプロジェクトに対してなら、まずは Lightweight Stream API や Retrolambda は導入しやすいと思うので、ここから初めて見ても良いと思います。