現場でほどんどjava8でコードを書いた事がないので、危機感を覚え学習したのでここにまとめます。
listの中身を数字に変換して出力するサンプルです。
まずはjava8以前の例。
List<String> suuzies = Arrays.asList("1", "2", null, "3");
for (String suuzi : suuzies) {
if (suuzi == null) {
continue;
}
System.out.println(Integer.parseInt(suuzi));
}
// 1 2 3
次はjava8。
List<String> suuzies = Arrays.asList("1", "2", null, "3");
suuzies.stream().filter(s -> Objects.nonNull(s)).map(s -> Integer.parseInt(s))
.forEach(s -> System.out.println(s));
// 1 2 3
素晴らしい!1行でおさまりましたし、可読性も上がりますね!
最初見たときは全く意味が分からず、何が可読性上がるだよ、、と思いましたが
理解するとスッキリします。
stream処理の順番は
1.コレクションからstream生成
2.中間処理
3.終盤処理
になります。
なので上記コードは、
1.コレクションからstream取得
2.nullを排除
3.生き残った値を数字に変換
4.出力
ですね!
2、3が中間処理、4が終盤処理に当たります。
中間処理は満足するまで処理を書くことができ、終盤処理は1つのみ記述可能です。
上記のように矢印で繋いだように処理を書けると考えると、
確かに可読性上がっているなぁと感じますね。
現場レベルだとifやforでネストがカオスになる事も多々あると思いますが、
これだと凄く読みやすくなりそうですよね。
ちなみに、java8.javaのコードは下記のように記述する事も出来ます。
List<String> suuzies = Arrays.asList("1", "2", null, "3");
suuzies.stream().filter(Objects::nonNull).map(Integer::parseInt)
.forEach(System.out::println);
// 1 2 3
メソッド参照というのを使っていてさらにコードが短くなりますが、
ここでは説明を割愛させて頂きます。
streamを理解した所で、ラムダ式についても少し書きます。
Stream APIには中間処理、終盤処理に使用するメソッドがいくつか用意されています。
例えば、上記に出てきたfilterメソッドはこのようになっています。
Stream<T> filter(Predicate<? super T> predicate);
僕は、最初見たときアレルギーを起こしました。すぐに閉じました。
filterの引数の中の(Predicate super T>)も覗いて見ましょう。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
これは関数型インターフェースです。
ラムダ式とは、関数型インターフェースを短い記述で実装できる物になります。
ちなみに関数型インターフェースとは、抽象メソッドが一つのみ定義されている物になります。
抽象メソッド以外のstaticやdefaultメソッドは含まれていても構いませんが、条件としては無視されます。
Predicateクラスでは'boolean test(T t)'のみが対象となり、他は無視されます。
このインターフェースは見ての通り、T型の引数を受け取りboolean型の戻り値を返します。
先ほどのコード(java8.java)のfilterの部分をもう一度。
filter(s -> Objects.nonNull(s))
Stream.classのfilterの引数を実装しています。
この部分がラムダ式という事になります!
他にも関数型インターフェースがいくつか用意されています。
java8.classで用いたmapだと、、
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
引数のFunctionの中身も見てみます。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
// and more
}
T型を引数に取り、R型を返すメソッドです!
つまり、受け取った引数を処理して別の形にして返却という事です!
先ほどのjava8.javaのmapの部分をもう一度。
map(s -> Integer.parseInt(s))
引数に文字を受け取り、数値に変換して返していますね!
ラムダ式の構文はこんな感じ。
( 引数 ) -> { 処理 }
抽象メソッドが一つだから出来る書き方ですね♪
他にも戻り値が無いConsumerや、引数を取らないSupplierなどがあります!
ラムダ式はStreamの為に出来たのかなと思います!
勉強当初は、抽象メソッド1つのインターフェースなんて定義する事あるのかな、、
とか思ってましたが、あらかじめ用意されている物を僕達開発者がラムダ式で実装するという
使い方をすれば確かに便利だと思いました!
僕もまだまだなので、これからもっと理解を深めていきます!