Stream#map()とStream#collect()を使ってListを生成する
Java8 から導入された Stream API も少しずつ慣れてきて、「そういえばこういうパターンはよく使っているな」というものがいくつかあるのでメモしておく。
今回はその1つ目。
目次
- 1. List に格納されているオブジェクトから必要なものだけをピックアップして新しい List を生成するというパターン
- 2. Before: Stream#forEach() を使って書いていたコード
- 3. After1: Stream#map() と Stream#collect() を使って List を生成する
- 4. After2: Stream#map() で行う処理を Function 型の変数に切り出す
- 5. 更新履歴
- 6. この記事の内容について
1. List に格納されているオブジェクトから必要なものだけをピックアップして新しい List を生成するというパターン
あるクラスのオブジェクトが List
に格納されていて、そのオブジェクトの特定のフィールドだけをピックアップした List
を生成したいというパターン。
検索結果として取得した Entity から特定のフィールドを表示用のクラスにコピーして List
に格納したい場合によく使っている。
「この手のコードはこうやって書くんだろう」と以前は思っていたが、Stream API に触れるうちに「実はこうやって書いたほうがそれっぽい」と気付きもあったので、Before と After の例も載せておく。
2. Before: Stream#forEach() を使って書いていたコード
まずは Before のコードから。
例として次のような Person
というクラスがある。
public class Person { /** * 名前。 */ private final String name; /** * 年齢。 */ private final int age; /** * 性別。 */ private final Gender gender; // Getter, Setter, コンストラクタは省略。 }
また、Person
クラスの name
だけを格納する PersonsName
というクラスもあったとする。
public class PersonsName { /** * 名前。 */ private final String name; // Getter, Setter, コンストラクタは省略。 }
Person
が複数格納されている List
があった場合に、それを元に PersonsName
が格納される List
を生成するには次のように書くものと思っていた。
@Test public void mapメソッドを使ってListを生成する_before1() throws Exception { // `Person` を格納した `List` List<Person> persons = Arrays.asList( // 1つ目の `Person` new Person("NAME_1", 40, Gender.MALE), // 2つ目の `Person` new Person("NAME_2", 30, Gender.FEMALE), // 3つ目の `Person` new Person("NAME_3", 20, Gender.MALE)); // `PersonsName` を格納する `List` // `Stream#forEach()` 内で生成したオブジェクトを格納する `List` は事前に用意しておかないとダメなんだと勘違いしていた List<PersonsName> actual = new ArrayList<>(); // `Stream#forEach()` を使って、`Person` から `PersonsName` を生成して ↑ で生成した `List` に詰める persons.stream().forEach(person -> { PersonsName personsName = new PersonsName(person.getName()); actual.add(personsName); }); // verify assertThat("生成された List のサイズも 3 である", actual.size(), is(3)); assertThat("先頭の名前が正しいこと", actual.get(0).getName(), is("NAME_1")); assertThat("最後の名前が正しいこと", actual.get(actual.size() - 1).getName(), is("NAME_3")); }
コメントにも書いたが、Stream#forEach()
の中では List
を作れないので、Stream#forEach()
を実行する前に PersonsName
を格納する List
は生成しておかなければいけないと思っていた。
また、従来の for 文が Stream#forEach()
になっただけで見た目にもそれっぽくない。
でも、「この手のコードはこうやって書くんだろう」と勘違いしていて、しばらくはこうやって書いていた。が、もっと違った書き方ができることを知った。
3. After1: Stream#map() と Stream#collect() を使って List を生成する
その After なコードは次の通り。
@Test public void mapメソッドを使ってListを生成する_after1() throws Exception { // setup List<Person> persons = Arrays.asList( // 1つ目の `Person` オブジェクト new Person("NAME_1", 40, Gender.MALE), // 2つ目の `Person` オブジェクト new Person("NAME_2", 30, Gender.FEMALE), // 3つ目の `Person` オブジェクト new Person("NAME_3", 20, Gender.MALE)); // `Stream#map()` を使って `Person` から `PersonsName` を生成し、`Stream#collect()` を使って処理結果を `List` に詰める List<PersonsName> actual = persons.stream().map(person -> new PersonsName(person.getName())).collect(Collectors.toList()); // verify assertThat("生成された List のサイズも 3 である", actual.size(), is(3)); assertThat("先頭の名前が正しいこと", actual.get(0).getName(), is("NAME_1")); assertThat("最後の名前が正しいこと", actual.get(actual.size() - 1).getName(), is("NAME_3")); }
「Stream
クラスのメソッドは『中間処理』と『終端処理』の2種類に分けられる」ということが徐々に理解できるようになってきて ↑ のように書けることに気づいた。
その「理解」とは、中間処理で処理してその結果を終端処理で生成するという流れのこと。
上記は Stream#map()
で処理を行い Stream#collect()
でその結果を生成している。結果は Collectors.toList()
によって List
に格納されていくという流れになっている。
何となく Java8 っぽい見た目になってきた。
4. After2: Stream#map() で行う処理を Function 型の変数に切り出す
Stream#map()
で Person
から PersonsName
を生成しているが、この処理が他でも使われるようなら関数型インタフェースとして Function
型の変数として切り出すこともできる。
@Test public void mapメソッドを使ってListを生成する_after2() throws Exception { // `Person` を格納した `List` List<Person> persons = Arrays.asList( // 1つ目の `Person` new Person("NAME_1", 40, Gender.MALE), // 2つ目の `Person` new Person("NAME_2", 30, Gender.FEMALE), // 3つ目の `Person` new Person("NAME_3", 20, Gender.MALE)); // `Person` クラスから `PersonsName` を生成する `Function` Function<Person, PersonsName> personToPersonsName = (person -> new PersonsName(person.getName())); // `Stream#map()` を使って `Person` から `PersonsName` を生成し、`Stream#collect()` を使って処理結果を `List` に詰める List<PersonsName> actual = persons.stream().map(personToPersonsName).collect(Collectors.toList()); // verify assertThat("生成された List のサイズも 3 である", actual.size(), is(3)); assertThat("先頭の名前が正しいこと", actual.get(0).getName(), is("NAME_1")); assertThat("最後の名前が正しいこと", actual.get(actual.size() - 1).getName(), is("NAME_3")); }
何となくだがさらに Java8 っぽい見た目になった気がする。
Function
型の変数として切り出すかは要求される内容によって判断すればいいとして、この「Stream#map()
で処理をして Stream#collect()
で収集する」というスニペットはよく使っているので覚えておくとよいかもしれない。
5. 更新履歴
- 2015/01/12
- 初版作成
6. この記事の内容について
この記事は Javaプログラマーなら習得しておきたい Java SE 8 実践プログラミング を読みながら個人的にまとめたことをメモした内容になっています。
調べ物から記事作成までの全て作業はプライベートな時間に行ったものであり、所属組織のリソースは消費していません。また、記事のアイデアも所属組織に由来していません。
記事の内容が必ずしも正しかったり、最新ではないおそれがあります。参考にされる分には構いませんが、記事の内容を取り入れたことによって発生した損害などの責任は負いません。ご利用は自己判断でお願いいたします。
Javaプログラマーなら習得しておきたい Java SE 8 実践プログラミング
- 著者Cay S. Horstmann
- 価格¥ 3,024(2014/10/17 時点)
- 出版日2014-09-22
- 商品ランキング24027 位
- 大型本264 ページ
- ISBN-104844336673
- 出版社インプレス
Googleアドセンス用(PC)
関連記事
-
-
プロセスの起動を確認するプログラム
プロセスが起動しているかを確認するプログラムを Java で書いてみました。Excel のプロセス確
-
-
Java から VBScript を使って Excel を印刷する
背景 Java のデスクトップアプリを作成していたときの要件に「Excel で作成された帳票を印刷
-
-
Spark を使って JSON を返すシンプルな API を書いてみた
必要に迫られてモックの API を作成することになった。 大した工数を割けないので使ったことのある
-
-
ファイルの拡張子が JPG であるかを問い合わせるスニペット
特に目新しいこともないが、同じようなことをする機会があるかもしれないのでメモ。Gist にも登録済み
-
-
Functions#compose() を使って Function を合成するサンプル(Java8 との比較付き)
Java8 と GoogleGuava の Function について コードの違いを書いた。 その
-
-
サーブレットでクラスパス上にあるプロパティファイルを参照するスニペット
よく使うわりにはいつも調べているのでいい加減メモすることにした。 "app.proper
-
-
SDKMAN!(GVM) を使って Mac に Gradle をインストールする
長らく Java のアプリ開発には Maven を使ってきたが、時代に取り残されている感じがするので
-
-
Exec Maven Plugin で maven コマンドでアプリを起動する
packaging タグに "jar" を指定した Maven プロジェクトをコマンドラインから起動
-
-
google-gson でシリアライズ・デシリアライズしたサンプル
これまで Java で JSON を扱うときは JSONIC を使っていたが、google-gson
-
-
InputStream に格納されているストリームデータのサイズを取得する
Jersey を使った Web アプリを作成中なんですが、 enctype="multipart/f