これまでなんとなくでラムダ式だったりStreamAPIをつかってきたので自分の再学習のため、この記事を作成しようと思いました。
間違っているところあるかもしれません、暖かく見守ってください。
そもそもラムダ式ってなんのためにあるのか?
そうです、そのレベルからです。なので調べました。
「ラムダ式のメリットは、匿名クラス(一度だけしか使わないようなクラス)を用いる場合よりも関数型インターフェース(抽象メソッドを1つだけ持つインターフェース)の記述が簡潔になることにあります。」
だそうです。
匿名クラスを用いた関数型インターフェースの記述よりも簡潔にするためのものなんですね。
関数型インターフェース
関数みたいなインターフェースで(そのまま)それぞれ一つずつ抽象メソッドを持っています。
関数合成という合体技もありますがここでは標準関数型のみ説明します。
インターフェース | メソッド | 説明 |
---|---|---|
Function | R apply (T t) | T型の引数を受け取って、R型の結果を返す |
Consumer | void accept (T t) | T型の引数を受け取って、結果は返さない |
Predicate | boolean test(T t) | T型の引数を受け取って、boolean値の結果を返す |
Supplier | T get() | 引数無し、T型の結果を返す |
UnaryOperator | T apply(T t) | T型の引数を受け取って、T型を返す |
実装例は下で紹介します。
匿名クラス
クラス定義とインスタンス化を一つの式で記述したものです。
一回きりしか使わないクラスを定義したい時などに役立ちます。
匿名クラスを用いない場合
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Human human = new Human();
String name = "shohei";
String result = human.apply(name);
System.out.println(result);
}
}
class Human implements Function<String, String> {
@Override
public String apply(String name) {
return "Hello " + name;
}
}
匿名クラスを用いる場合
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
String result = new Function<String, String>() {
public String apply(String name) {
return "Hello " + name;
}
}.apply("shohei");
System.out.println(result);
}
}
このようになんとなく簡潔に記述ができています。
ただやはり匿名クラスはこの後呼び出ししたくてもできないので注意が必要ですね。
そしてラムダ式を使うとさらにコードを簡略化できるはずです!
ラムダ式の構文は以下です。
( 実装するメソッドの処理 ) -> { 処理 };
(型推論を用いたラムダ式の構文の省略がありますが今回は割愛します)
ラムダ式を用いた場合
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, String> result = (String name) -> {
return "Hello" + name;
};
}
}
とっても可読性が高まりましたね、さすがラムダ式です。
このようにjavaの歴史を辿るとなぜ匿名クラスやラムダ式があるのかがわかりました。
ラムダ式にはいくつかの特徴があります
変数のスコープ:ラムダ式から参照されるローカル変数はfinal、または事実上のfinalである必要がある
コンパイラの型推論: ラムダ式では、コンパイラが型を推論するため、通常は型を明示的に指定する必要がありません。これにより、冗長なコードを減らすことができます
などです
StreamAPIについて
ラムダ式について再学習したところでようやくStreamAPIについてまとめます。
StreamAPIとは
コレクションや配列などのデータ要素に対する高レベルな操作を提供します。
StreamAPIを使用することで、Listやデータの集合体に対する操作を簡単に行うことができます。
使い方
StreamAPIを使うには大きく3つに分けた操作を行う必要があり、以下の順で行います。
1.Streamの生成
2.中間操作(複数可能)
3.終端操作
Streamの生成は配列や文字列、コレクションから行うことができます。
ここからはListでStreamを操作する手順を紹介します。
以下はListからStreamを生成し、出力する手順です。
import java.util.stream.*;
import java.util.List;
import java.util.Arrays;
class Main{
public static void main(String[] args){
List<String> list = Arrays.asList("tanaka", "suzuki", "satou");
// ListからのStreamの生成
Stream<String> stream = list.stream();
// Streamの要素を出力する
stream.forEach(System.out::println);
}
}
実行結果
tanaka suzuki satou
お次は中間操作ですね。
中間操作をしたのちに新しいStreamを返します。
中間操作の種類はいろいろありますが中でもよく使いそうなものをピックアップします。
他にもあるので調べてみてください。
メソッド | 内容 |
---|---|
filter(Predicate p) | 要素が条件に合致しない場合、その要素を削除 |
map(Function f) | 格納された全ての要素を変換する |
distinct() | ストリーム内の重複要素を削除 |
sorted(Comparator c) | 要素をソート |
以下のコード例はlist変数から5文字以下の要素を削除した新規のStreamを返します。
中間操作はいくつもつなげることができます。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
class Main{
public static void main(String[] args){
List<String> list = Arrays.asList("tanaka", "suzuki", "satou");
// ListからのStreamの生成
Stream<String> stream = list.stream();
Stream<String> stream2 = stream.filter(str -> str.length() > 5);
stream2.forEach(System.out::println);
}
}
実行結果
tanaka suzuki
最後は終端操作です
終端操作にもいろいろ種類がありますがピックアップします。
メソッド | 戻り値型 | 内容 |
---|---|---|
forEach(Consumer action) | void | 各要素に対してactionを実行する。順序を保証しない |
toArray() | Object[] | 配列化する |
min() | Optional | 最小要素を返す。リダクションの特殊な場合 |
max() | Optional | 最大要素を返す。リダクションの特殊な場合 |
count() | long | 要素数を返す。リダクションの特殊な場合 |
anyMatch(Predicate p) | boolean | p(e)の評価結果が1つでもtrueになる場合にtrueを返す |
allMatch(Predicate p) | boolean | p(e)の評価結果がすべてtrueになる場合にtrueを返す |
findFirst() | Optional | 最初の要素を返す。順番を保持しないストリームでは任意の要素を返す |
findAny() | Optional | 任意の要素を返す |
以下のコードはfilterにかけたListの要素数を返します。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
class Main{
public static void main(String[] args){
List<String> list = Arrays.asList("tanaka", "suzuki", "satou");
// ListからのStreamの生成
Stream<String> stream = list.stream();
Stream<String> stream2 = stream.filter(str -> str.length() > 5);
System.out.println(stream2.count());
}
}
実行結果
2
Stream生成と中間操作の際のコードの
stream2.forEach(System.out::println);
のforEachも終端操作ですね。
まとめ
今回はラムダ式とかStreamAPI周りについて調べながらアウトプットしてみました。
私もラムダ式がなぜ必要なのかはあまり考えなかったですし、再学習できてとても有意義なものになったと思います。
個人的にはfor文とかif文とかを使ってコレクションを操作するよりもStreamを使ったほうがスマートでかっこいいかなと思っています。(可読性も上がりますしね)
最後まで読んでいただいてありがとうございました!