Java SE8 Stream APIに関して
Lambda式ともに、Stream APIがJava SE 8に追加されています。
この2つの機能が追加されたことにより、
Collection Libraryの操作に関して、強化されました。
なにが強化されたか?と言うと、
Collectionは、それに含まれる要素を操作する場合、イテレータを使って、処理します。
拡張for文がそれにあたるのですが、これは、外部イテレータと言われるやり方で、
今度の追加されたLambda式とStream APIを使うやり方は、内部イテレータになるのだと思います。
例えば、下の様なPersonクラスのリストを処理する場合
public class Person { private String id; private String name; public Person(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public String getName() { return name; } }
今までは、拡張for文(外部イテレータ)で処理していました。
下の様に拡張for文で、リストから順次取り出して、処理するやりです。
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<>(); personList.add(new Person("100", "person1")); personList.add(new Person("200", "person2")); personList.add(new Person("300", "person3")); personList.add(new Person("400", "person4")); for (Person person : personList) { System.out.println("id : "+person.getId()+" name : "+person.getName()); } } }
今度の追加されたLambda式とStream APIを使うやり方の場合、
下の様な記述になります。
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("100", "person1")); personList.add(new Person("200", "person2")); personList.add(new Person("300", "person3")); personList.add(new Person("400", "person4")); Stream<Person> stream = personList.stream(); stream.forEach(p -> System.out.println("id : "+p.getId()+" name : "+p.getName())); } }
下の様な書き方もできます(恐らく、こちらの方がいいかなぁ)。
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<>(); personList.add(new Person("100", "person1")); personList.add(new Person("200", "person2")); personList.add(new Person("300", "person3")); personList.add(new Person("400", "person4")); personList.stream().forEach(p -> System.out.println("id : "+p.getId()+" name : "+p.getName())); } }
ちなみにマルチコアの対応も入っていて、
通常、下の場合は、シングルスレッドで動いているので、結果も順次処理された内容になります。
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<>(); personList.add(new Person("100", "person1")); personList.add(new Person("200", "person2")); personList.add(new Person("300", "person3")); personList.add(new Person("400", "person4")); personList.stream().forEach( p -> { try { Thread.sleep(3000); LocalTime time = LocalTime.now(); System.out.println("time : "+time.toString()+" id : "+p.getId()+" name : "+p.getName()); } catch (Exception e) { } }); } }
time : 01:17:45.374 id : 100 name : person1 time : 01:17:48.376 id : 200 name : person2 time : 01:17:51.377 id : 300 name : person3 time : 01:17:54.378 id : 400 name : person4
stream()をparallelStream()に変更するだけで、並列で動くようになります。
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<>(); personList.add(new Person("100", "person1")); personList.add(new Person("200", "person2")); personList.add(new Person("300", "person3")); personList.add(new Person("400", "person4")); personList.parallelStream().forEach( p -> { try { Thread.sleep(3000); LocalTime time = LocalTime.now(); System.out.println("time : "+time.toString()+" id : "+p.getId()+" name : "+p.getName()); } catch (Exception e) { } }); } }
time : 01:19:33.308 id : 400 name : person4 time : 01:19:33.308 id : 100 name : person1 time : 01:19:33.308 id : 200 name : person2 time : 01:19:33.308 id : 300 name : person3
Java SE8以前は、このようなことをする場合、開発がかなりがんばらないいけなかったのですが、
今回の機能追加で、簡単に書けるようになりました。
ただし、スレッドセーフかどうかは、気をつけない駄目ですけど!
例えば、Listに格納されたエンティティをDTOのListに入れ替えるとの場合、
下の様に書くと問題がでます。
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("100", "person1")); personList.add(new Person("200", "person2")); personList.add(new Person("300", "person3")); personList.add(new Person("400", "person4")); List<PersonDto> personDtoList = new ArrayList<>(); personList.parallelStream().forEach( p -> { personDtoList.add(new PersonDto(p.getId(), p.getName())); }); } }
ArrayListは、スレッドセーフでは、ないので、addメソッドを呼ぶと、内部で、サイズを拡張しているのですが、
この時、並列で動いている場合、indexがおかしなったりして、エラーになったりします。
恐らく、数件だったら大丈夫かもしれませんが、かなりの件数を処理すると現象が出るかもしれません。
なので、下の様に、synchronizedをかます必要があるかと思います。
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("100", "person1")); personList.add(new Person("200", "person2")); personList.add(new Person("300", "person3")); personList.add(new Person("400", "person4")); List<PersonDto> personDtoList = new ArrayList<>(); personList.parallelStream().forEach( p -> { synchronized(personDtoList) { personDtoList.add(new PersonDto(p.getId(), p.getName())); } }); } }
次にOptionalというものがあって、中間処理みないな機能があります。
メソッド名 | 機能 |
filter(Predicate predicate) | 条件にマッチ(true)した場合のみを対象にする |
limit(long maxSize) | 最初の要素からmaxSizeまでを対象にする |
distinct() | 重複要素を除外する |
map(Function function) | 処理により生成された要素をStreamとし戻します |
flatMap(Function> function) | 処理により生成された複数の要素をStreamとして戻します |
sorted(Comparator comparator) | Comparatorの結果を元に並び替えして、Streamを戻します |
Stream APIのfilterの使い方。
1〜10までの整数を格納したリストの内、5以上のみ表示する処理の例です。
public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); list.stream().filter(s -> s > 5).forEach(System.out::println); } }
Stream APIのlimitの使い方
1〜10までの整数を格納したリストの内、一番最初要素から3つまで表示する処理の例です。
public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); list.stream().limit(3).forEach(System.out::println); } }
Stream APIのdistinctの使い方
重複した要素がある場合、重複したもの除いて、結果を表示する処理の例です。
public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); list.stream().distinct().forEach(System.out::println); } }
このdistinctは、Objectクラスのequalsメソッドで、比較をしているので、
POJOのリストから重複を除外する場合は、equalsメソッドをオーバーライドする必要があります。
例えば、下の様に、Personオブジェクトのリストをdistinctを使って、重複を除外する場合、
equalsメソッドをオーバーライドしないままでは、
重複は、除外されずに、全て表示されてしまいます。
public class Main { public static void main(String[] args) { List<Person> personList = getPersonList(); personList.stream().distinct().forEach(System.out::println); } public static List<Person> getPersonList() { List<Person> personList = new ArrayList<>(); personList.add(new Person(200, "二郎")); personList.add(new Person(100, "一郎")); personList.add(new Person(400, "四郎")); personList.add(new Person(300, "三郎")); personList.add(new Person(200, "二郎")); personList.add(new Person(100, "一郎")); personList.add(new Person(400, "四郎")); personList.add(new Person(300, "三郎")); return personList; } }
重複を除外する場合は、下の様に、equalsメソッドをオーバーライドする必要があります。
public class Person { private Integer id; private String name; public Person(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public String getName() { return name; } @Override public boolean equals(Object obj) { if (obj instanceof Person) { return ((Person) obj).getId().equals(id) && ((Person) obj).getName().equals(name); } return false; } }
Stream APIのmapの使い方
Listに格納されたエンティティをDTOのListに入れ替える処理の例です。
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("100", "person1")); personList.add(new Person("200", "person2")); personList.add(new Person("300", "person3")); personList.add(new Person("400", "person4")); List<PersonDto> personDtoList = new ArrayList<>(); personList .stream() .map(s -> { PersonDto personDto = new PersonDto(s.getId(), s.getName()); return personDto;}) .forEach(personDto -> personDtoList.add(personDto)); } }
mapの処理結果がListなどの場合は、flatMapを使うぽいです。
Stream APIのsortedの使い方
Listに格納された要素を比較して、並び替える例
public class Main { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("100", "あああ")); personList.add(new Person("200", "えええ")); personList.add(new Person("300", "いいい")); personList.add(new Person("400", "ううう")); List<PersonDto> personDtoList = new ArrayList<>(); personList .stream() .sorted((p1, p2) -> p1.getName().compareTo(p2.getName())) .forEach(p -> System.out.println("id : "+p.getId()+" name : "+p.getName())); } }
上記以外にも、まだいろいろなメソッドがあるぽいのですが、
それは、まだ調べてないので、今度、調べて、更新することにします。