Java8のラムダ式とStream APIを利用してコーディング量の削減サンプル集
公開日:
:
最終更新日:2016/12/05
Java Java8, Stream API, ラムダ式
Java8になりラムダ式と「Stream API」が利用できるようになりました。
C#では一足早くラムダ式が導入されており、Javaでも早く利用したいと考えていたので、この新機能はとても嬉しかったです。
(ってもう結構時間経ってますが・・・)
Java8ではラムダ式だけでなく「Stream API」も利用できるので、この点ではC#を抜きかえしました。
(そんなことはどうでもいいですよね・・・)
とは言え、「さあラムダ式書こう!」と思ってもなかなか思い出せないんですよね・・・
集中的にコーディングしている時はサクサク出てくるのですが、ちょっと使ってないと、あれどうやって書くんだっけ?みたいな。
と言う事で、本エントリーでは、機能的な説明ではなく、実際のコーディングでよく利用するラムダ式と「Stream API」のサンプル集となります。
本エントリーの内容は以下の通りです。
- Listのソート処理
- 重複のないListの取得
- Listの要素の除去
前準備
説明で利用するテストクラス
実際の動作が意識しやすいようにJUnitのテストケースとして
サンプル実装と、期待する動作を検証する形のコードとなります。
以下がクラスの定義となります。
1 2 3 4 5 6 7 8 9 10 11 |
package example; import static org.junit.Assert.*; import java.util.Arrays; import java.util.List; import org.junit.Test; public class LambdaSampleTest { } |
説明で利用するテストデータクラス
Listの要素に利用するテストデータクラスPersonは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package example; public class Person implements Comparable{ private String name; private int age; public String getName() { return name; } public int getAge() { return age; } public Person(String name, int age) { this.name = name; this.age = age; } public int compareTo(Object other) { Person otherPerson = (Person)other; int nameCompResult = 0; nameCompResult = this.name.compareTo(otherPerson.name); //ageが同じであればnameが大きい方を先、それ以外は年齢順とする if(this.age == otherPerson.age) { return nameCompResult; } else if(this.age > otherPerson.age) { return 1; } else { return -1; } } } |
以降では、上記クラスにテストケースを追加して行きますので、追加したテストケースのみを記載させていただきます。
Listのソート処理
文字列のソート処理
Listを対象としたソート処理となります。
昇順の例
まずは昇順の例です。実際のソート処理は6行目です。文字列のcompareToの値によってソート順を判断しています。
1 2 3 4 5 6 7 8 9 10 11 12 |
@Test public void sortAscStringList() throws InterruptedException { //ソート対象のリストを宣言 List<String> list = Arrays.asList("D", "C", "B", "A"); //ソート(昇順) list.sort((str1, str2) -> str1.compareTo(str2)); //assertArrayEqualsで検証するために配列に変換 String[] actual = (String[]) list.toArray(new String[list.size()]); //昇順にソートされているか検証 assertArrayEquals(new String[]{"A", "B", "C", "D"}, actual); } |
降順の例
次に降順の例です。ソートに利用するラムダ式の値に-1をかけている点がポイントです。
ラムダ式の値に-1をかけることによって文字列のソートの判断ロジックが反転しています。
後は、テストデータと期待値が異なるだけです。
1 2 3 4 5 6 7 8 9 10 11 12 |
@Test public void sortDescStringList() throws InterruptedException { //ソート対象のリストを宣言 List<String> list = Arrays.asList("A", "B", "C", "D"); //ソート(降順) list.sort((str1, str2) -> str1.compareTo(str2) * -1); //assertArrayEqualsで検証するために配列に変換 String[] actual = (String[]) list.toArray(new String[list.size()]); //降順にソートされているか検証 assertArrayEquals(new String[]{"D", "C", "B", "A"}, actual); } |
Personのソート処理
Listを対象としたソート処理となります。
年齢の昇順の例
年齢の昇順の例なのですが、まずテストメソッドの実装前にテストで利用するPersonを宣言しておきます。
このインスタンスを利用してソート対象のリストや期待値のリストを作成するためです。
ポイントは11行目です。PersonのgetAgeの大きい方が先にソートされるようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private static Person TAROU = new Person("TAROU", 15); private static Person JIROU = new Person("JIROU", 10); private static Person SABUROU = new Person("SABUROU", 8); private static Person HANAKO = new Person("HANAKO", 15); @Test public void sortAscPersonList() throws InterruptedException { // ソート対象のリストを宣言 List<Person> list = Arrays .asList(new Person[] { SABUROU, JIROU, TAROU }); // 年齢でソート(昇順) list.sort((pserson1, pserson2) -> pserson2.getAge() - pserson1.getAge()); // assertArrayEqualsで検証するために配列に変換 Person[] actual = (Person[]) list.toArray(new Person[list.size()]); // 昇順にソートされているか検証 assertArrayEquals(new Person[] { TAROU, JIROU, SABUROU }, actual); } |
年齢の降順の例
こちらもポイントは11行目です。年齢の昇順の例との違いはpserson1のageからpserson2のageを引いた値をソート順の判定基準としていることです。
このようにpserson1とpserson2の順番を入れ替えることによって昇順と降順を反転できます。
ソート対象のデータと期待値が異なる
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Test public void sortDescPersonList() throws InterruptedException { // ソート対象のリストを宣言 List<Person> list = Arrays.asList(new Person[] { TAROU, JIROU, SABUROU}); // 年齢でソート(昇順) list.sort((pserson1, pserson2) -> pserson1.getAge() - pserson2.getAge()); // assertArrayEqualsで検証するために配列に変換 Person[] actual = (Person[]) list.toArray(new Person[list.size()]); // 昇順にソートされているか検証 assertArrayEquals(new Person[] { SABUROU, JIROU, TAROU}, actual); } |
前の方でも記載させていただきましたが11行目は以下のように記載してもOKです。
1 |
list.sort((pserson1, pserson2) -> (pserson2.getAge() - pserson1.getAge()) * -1); |
年齢と名前の昇順の例
SQLではよくありますよねOrder Byに複数の条件を指定する時、そんな例となります。
Streamが遂に登場です。8行目ですね。
Personのageとnameで昇順ソートしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Test public void sortAscByAgeAndNamePersonList() throws InterruptedException { // ソート対象のリストを宣言 List<Person> list = Arrays .asList(new Person[] { TAROU, HANAKO, JIROU, SABUROU }); // 年齢と名前の昇順 Stream<Person> sortedList = list.stream().sorted(Comparator.comparingInt(Person::getAge) .thenComparing(Comparator.comparing(Person::getName))); // assertArrayEqualsで検証するために配列に変換 Person[] actual = (Person[]) sortedList.toArray(Person[]::new); // 昇順にソートされているか検証 assertArrayEquals(new Person[] { SABUROU, JIROU, HANAKO, TAROU }, actual); } |
Personクラスはjava.lang.Comparableを実装し、compareToメソッドを実装していますので
8行目は
1 |
Stream<Person> sortedList =list.stream().sorted(Comparator.naturalOrder()); |
や
1 |
Stream<Person> sortedList =list.stream().sorted(); |
に書き換えることができます。
デフォルトのソートロジックが存在して、そのロジックを頻繁に利用する場合はcompareToメソッドを実装し、それを利用するとDRYなコードになりますのでオススメです。
年齢と名前の降順の例
Comparator.comparing(Person::getName)
と
Comparator.comparing(Person::getName)
に.reversed()を付与して結果を反転させています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Test public void sortDescByAgeAndNamePersonList() throws InterruptedException { // ソート対象のリストを宣言 List<Person> list = Arrays .asList(new Person[] { TAROU, SABUROU, HANAKO, JIROU, }); // 年齢でソート(降順) Stream<Person> sortedList = list.stream().sorted(Comparator.comparingInt(Person::getAge).reversed() .thenComparing(Comparator.comparing(Person::getName).reversed())); // assertArrayEqualsで検証するために配列に変換 Person[] actual = (Person[]) sortedList.toArray(Person[]::new); // 昇順にソートされているか検証 assertArrayEquals(new Person[] { TAROU, HANAKO, JIROU, SABUROU}, actual); } |
年齢と名前の昇順の例と同様に8行目は
1 |
Stream<Person> sortedList =list.stream().sorted(Comparator.reverseOrder()); |
に書き換えることができます。
重複のないListの取得
Listを対象とした処理
Streamのdistinctで実現できます。
1 2 3 4 5 6 7 8 9 10 11 12 |
@Test public void distinctList() throws InterruptedException { // ソート対象のリストを宣言 List<String> list = Arrays.asList("A", "B", "C", "D", "A", "B"); // 重複を除いたリストを取得 Stream<String> distinctList = list.stream().distinct(); // assertArrayEqualsで検証するために配列に変換 String[] actual = (String[]) distinctList.toArray(String[]::new); // 降順にソートされているか検証 assertArrayEquals(new String[] { "A", "B", "C", "D" }, actual); } |
Listの要素の除去
要素の除去と記載していますが、条件に合致した要素の取得も同じ考え方です。
Listを対象とした処理
“A”から始まらない文字列のリストを取得する例となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Test public void removeElementFromList() throws InterruptedException { // ソート対象のリストを宣言 List<String> list = Arrays.asList("A", "B", "C", "D", "AA", "BB"); //Aから始まらない文字列だけのリストを取得 Stream<String> resultList = list.stream().filter(element -> !element.startsWith("A")); // assertArrayEqualsで検証するために配列に変換 String[] actual = (String[]) resultList.toArray(String[]::new); // 降順にソートされているか検証 assertArrayEquals(new String[] { "B", "C", "D", "BB" }, actual); } |
Listを対象とした処理
namaの長さが5以下でageが10以下のリストを取得する例となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Test public void removePersonFromList() throws InterruptedException { // ソート対象のリストを宣言 List<Person> list = Arrays .asList(new Person[] { TAROU, HANAKO, JIROU, SABUROU }); // namaの長さが5以下でageが10以下のリストを取得する例となります。 Stream<Person> resultList = list.stream().filter(person -> person.getName().length() <= 5 && person.getAge() <= 10); // assertArrayEqualsで検証するために配列に変換 Person[] actual = (Person[]) resultList.toArray(Person[]::new); // 昇順にソートされているか検証 assertArrayEquals(new Person[] { JIROU }, actual); } |
関連記事
-
-
Selenium入門その4[Selenium WebDriver(Java/Junit4)の基本コマンド]
前回はEclipse環境でSelenium WebDriverでJava/Junit4を利用したテス
-
-
お勧め本紹介[新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)]
リファクタリング―プログラムの体質改善テクニック (Object Technology Series
-
-
JUnit入門その3[Eclipse4.4のJUnitプラグインとMockフレームワークのJMockitの併用の基本]
JUnit入門その3ではMockフレームワークのJMockitを利用したテスト環境の構築と使い方につ
-
-
Eclipseの使い方(Windows環境のEclipse4.3、Eclipse4.4)
Eclipse4.4.0よりJDK8を正式サポートするそうです。 Eclipseトップレベルプ
-
-
JDK8(java 8)の新機能のラムダ式の利用方法[その3:java8が用意している関数型インターフェース]
[その2:関数型インターフェースを例としたラムダ式] と説明させていただいておりますが、
-
-
Selenium入門その6[Selenium3でWebDriver(Java/Junit4)の環境を作成しEdge,Chrome,Firefoxで確認してみる]
Selenium3も3.0.1がリリースされましたし、今後は本格的にSelenium3が利用されてい
-
-
Eclipse(4.4)でJava言語のリファクタリング機能の使い方[「メソッド・シグニチャーの変更」と基本的な抽出処理、及び「インライン化」、「定数の抽出」]
本エントリーでは、Eclipse(4.3)でJava言語のリファクタリング機能の使い方に引き続き
-
-
Java超入門 with Eclipse[1:Eclipse環境の作成とEclipseの使い方の説明]
「JDT betaを利用してJDK8(java 8)対応のEclipse開発環境を作成する」では、最
-
-
Selenium利用時のトラブルシューティング方法[クリック編]
Seleniumは便利なテスト自動化ツールですし、今後は更なる利用者の増加が見込まれます。 とは言
-
-
Javaによる非同期処理入門その1[非同期処理の実装方法の概説]
Javaによる非同期処理に関するエントリーを前々から作成したいと思っていたのですが、ついに作成してみ