今更ですが、Java8 (特にStreamAPI) について試したので、まとめ。
Java8
Java8で追加・変更された主な機能は以下の3つ。
- Lambda
- StreamAPI
- Join/Fork (Java7〜)
この3つの機能はお互い関連しあっていて、LambdaはStreamAPIを簡潔に書けるようにするし、Join/Forkは、StreamAPIを介して ( .parallel()とかで) 簡単に使えるようになっている。
ということで、メインはStreamAPIについてですが、Lambda、Join/Forkの話も簡単に書きます。
Lambda
Consumer<String>
というClassを無名newして、accept(String t)
をOverrideしています.
このコードをLambdaを使って簡潔に書いてみます.
Stream.of("Taro", "Hanako", "John")
.forEach(new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
});
まず、Classの宣言とメソッド名までを消せます.
Stream.of("Taro", "Hanako", "John")
.forEach((String t) -> {
System.out.println(t);
});
引数の型も消せます. 一個しか無い時は括弧も消せます.
Stream.of("Taro", "Hanako", "John")
.forEach(t -> {
System.out.println(t);
});
本文が1行しか無い時は、括弧とセミコロン, Returnを消せます.
Stream.of("Taro", "Hanako", "John")
.forEach(t -> System.out.println(t));
値をメソッドにそのまま渡すだけの場合、メソッド参照に書き換えられます.
Stream.of("Taro", "Hanako", "John")
.forEach(System.out::println);
StreamAPI
StreamAPIとは、 (乱暴な言い方をすると) 拡張for文を置き換える ものです.
例えば以下のような、listをforeachして加工&出力するコードがあります。
- listからジョンを取り除く
- "hello XXX" な文章を作る.
- 出力する.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
// (1) listからジョンを取り除く
List<String> list2 = new ArrayList<>();
for(String name : list1) {
if(!name.equals("John")) {
list2.add(name);
}
}
// (2) "hello XXX" な文章を作る.
List<String> list3 = new ArrayList<>();
for(String name : list2) {
String sentence = "hello " + name;
list3.add(sentence);
}
// (3) 出力する.
for(String sentence : list3) {
System.out.println(sentence);
}
StreamAPIを使って書くと、こう↓なります.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
list1.stream()
.filter(s -> !s.equals("John"))
.map(s -> "hello " + s)
.forEach(System.out::println);
コードが簡潔になって見やすくなるのもメリットですが、一番重要なのは、 実行ステップ行が減って、バグも比例して少なくなる という事だと思います.
Java8以降は、for文を使ったコードはレガシーコードとなって、Gotoみたいに「使ったらカッコ悪いコード」となる日が来るのかもしれません...
Stream class
先ほどの例で、 .filter
, .map
, .forEach
等のメソッド呼び出しをしていますが、これらは全てStream
クラスのメソッドです.
Stream
クラスの生成の仕方にはいくつか方法があります.
List, Setから生成できます.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
Stream<String> stream = list1.stream();
Stream.of
で直接生成できます.
Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
数値の場合、範囲指定で生成できます.
IntStream stream = IntStream.range(1, 5);
Streamのサブクラスには、IntStream, LongStream, DoubleStream等があります.
値を処理する
リストの要素を全て順番に出力してみます.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
list1.stream()
.forEach(System.out::println);
値を変換する
リストの要素に "hello " をつけて (変換) 、出力してみます.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
list1.stream()
.map(s -> "hello " + s)
.forEach(System.out::println);
要素を取り除く
リストの"John"要素を取り除いて、出力してみます.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
list1.stream()
.filter(s -> !s.equals("John"))
.forEach(System.out::println);
個数を制限する
リストの要素を2つだけ、出力してみます.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
list1.stream()
.limit(2)
.forEach(System.out::println);
ソートする
リストの要素をソートして、出力してみます.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
list1.stream()
.sorted()
.forEach(System.out::println);
条件を満たす要素があるか判定する
リストの要素に"Hanako"があるか、判定してみます.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
boolean exist = list1.stream()
.anyMatch(s -> s.equals("Hanako"));
リストにして返す
Streamをリストにして返してみます.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
List<String> list2 = list1.stream()
.collect(Collectors.toList());
.parallel (Join/Fork)
StreamAPIを使うと、並列化プログラミングが簡単にできます.
今まで list.stream()
だった所を list.parallelStream()
にします. それだけです.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
list1.parallelStream()
.map(s -> "hello" + s)
.sorted()
.forEach(System.out::println);
実行してみるが、ソートされていない...
Taro
John
Hanako
出力もMulti-threadで処理されているからですね.
出力部分だけ直列に戻します. .sequential()
を追加するだけです.
List<String> list1 = Arrays.asList("Taro", "Hanako", "John");
list1.parallelStream()
.map(s -> "hello" + s)
.sorted()
.sequential()
.forEach(System.out::println);
ちゃんと順番に表示されます.
Hanako
John
Taro
サンプルコード
ここで述べたもの以外にも、大量の便利なAPIがあります.
StreamAPIについて、ひと通り試して纏めたコードです.