Java SE8 Stream APIに関して

Lambda式ともに、Stream APIJava 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()));
	}
}

上記以外にも、まだいろいろなメソッドがあるぽいのですが、
それは、まだ調べてないので、今度、調べて、更新することにします。