現在、参画しているプロジェクトではjavaを用いて開発しています。
その際、StreamAPIと拡張for文を使う場面が多く、それぞれの違いや、場面によっての使い分けを例をあげてまとめてみました。
#Stream API とは
Stream APIとはJava SE 8から追加されたイテレーションの拡張APIです。
ListなどのCollectionを扱う為のもので、値の集計やデータを使った処理などが出来る便利なAPIです。
基本的な流れ
- コレクションからstreamを取得
- streamに対して「中間操作」を実行
- 「終端操作」で変換したコレクションの中身に対して処理を適用
###中間操作の例
メソッド | 内容 |
---|---|
filter() | ( )内の式がtrueの要素だけを集める |
map() | 要素を変換 |
distinct() | ストリーム内の重複要素を削除 |
sorted() | Comparableの実装クラスに対する、自然順序でのソート |
###終端操作の例
メソッド | 戻り値型 | 内容 |
---|---|---|
forEach(Consumer action) | void | 各要素に対してactionを実行。順序を保証しない |
collect(Supplier factory, BiConsumer acc, BiConsumer combiner) | 結果コンテナ | factoryで生成した可変コンテナに対し、accで要素を追加し、combinerで各コンテナを結合 |
count() | long | ストリーム内の重複要素を削除 |
findFirst() | Optional | 最初の要素を返す。順番を保持しないストリームでは任意の要素を返却 |
findAny() | Optional | 任意の要素を返却 |
※collect の一例として、中間操作された要素をリストにして返すcollect(Collectors.toList()) などがある。 |
#StreamAPIと拡張for文を使った例
サンプル:「name(名前)とage(年齢)をフィールドに持つPersonクラス」
public class Person {
private String name;
private int age;
public String getName() { return this.name; }
public int getAge() { return this.age; }
/* コンストラクタやsetterは省略 */
■Personクラスのリストから年齢が20歳以上の人の、名前のリストを返却するメソッドを各構文で比較
●拡張for文を使用
public List<String> getAdultPersonNameList(List<Person> personList) {
List<String> adultPersonNameList = new ArrayList<>();
for (Person person : personList) {
if (person.getAge() >= 20) {
adultPersonNameList.add(person.getName());
}
}
return adultPersonNameList;
}
●StreamAPIを使用
public List<String> getAdultPersonNameList(List<Person> personList) {
return personList.stream() // streamの取得
.filter(person -> person.getAge() >= 20) // 中間操作(filter)
.map(Person::getName) // 中間操作(map)
.collect(Collectors.toList()); //終端操作(collect)
}
拡張for文を使用するパターンでは「20歳以上の人であればリストに追加」という構造になっています。
StreamAPIを使用するパターンでは「20歳以上の人を集める」→「集めた要素をそれぞれの名前に変換」→「リストにして返却」という構造になっています。
StreamAPIを使用することで可読性が上がるほか、部品性やコードの抽象度も高まっています。
また、中間操作は繋げて書くことができるため、より複雑な操作を行う際に、さらにその効果は発揮されそうです。
#StreamAPIが使えないパターン
Streamが使えないパターンというのも残念ながら存在しています。
Streamの処理は一般的なforループとは違って、continueやbreak、returnなどで一部の処理をスキップしたり途中で処理を止めることができません。基本的にStreamは全要素に対して処理をすることを前提にして設計されたからです。
使えないパターンではfor文で処理しましょう。
実際のパターンは以下の通りです。
###●複数の変数をまとめて扱いたい場合
1つのCollectionを扱うStreamはこの操作が苦手です。
複数の変数をStreamで扱おうとしてデータを作成するぐらいなら素直にfor文で処理させた方が良いことも多いです。
現在、私が参画しているプロジェクトでもこの場面は非常に多いです。
###●CollectionにIndexが欲しい場合
残念ながらJavaにおいて、Stream操作内でIndexを付与して処理されることは出来ません。
ランキング処理のような順位を付けたい場合など、Indexを付与したい場面ではfor文で処理しましょう。
###●検査例外を扱いたい場合
StreamというよりはJavaのラムダ式の制限になります。
ラムダ内で発生した検査例外についてはラムダ内部で処理する必要があります。
catchして無理矢理どうにかするという方式をとれば使えなくないのですが、あまりクールではありません。
以下の記事が分かりやすいので、気になる方は見てみてください。
[Java] Streamと例外処理は相性が悪いという話