主にラムダ式について書きます。
ラムダ式は何ができるのか
メソッドを変数のように扱える。
メソッドの引数にメソッドを渡せる。
匿名クラスと同じことをより簡単な記述で書ける。
ラムダ式のお決まり事
関数型インターフェースの抽象メソッドをオーバーライドする必要がある。
※関数型インターフェース = 1つだけ抽象メソッドを持つインターフェース。
基本の書き方
(型 引数名) ー> {処理1;};
(Object o) -> {System.out.println(o);};
普通 → 匿名クラス → ラムダ式 → メソッド参照の順番に書いてみる
ラムダ式は実は抽象メソッドをオーバーライドしているだけなのです。
それを理解するために
・普通にオーバーライド
・匿名クラスでオーバーライド
・ラムダ式でオーバーライド
・メソッド参照
という4つで書いてみます。
今回は関数型インターフェースConsumerの抽象メソッドacceptをオーバーライドして実装していきます。
普通
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//...
}
まずはConsumerインターフェースを「implements」で普通に実装して、acceptメソッドをオーバーライドする方法です。
import java.util.function.Consumer;
public class Lambda2 implements Consumer<String> {
@Override
public void accept(String s) {
System.out.println(s);
}
public static void main(String[] args) {
Lambda2 lam2 = new Lambda2();
lam2.accept("普通");
}
}
匿名クラス
次に匿名クラスです。
@Overrideのところがポイント。
import java.util.function.Consumer;
public class lambda3 {
public static void main(String[] args) {
Consumer<String> func = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
func.accept("匿名クラス");
}
}
ラムダ式
最後にラムダ式でConsumerインターフェースを実装する方法。
acceptメソッドをラムダ式でオーバーライドしています。
import java.util.function.Consumer;
public class Lambda1 {
public static void main(String[] args) {
Consumer<String> func = (s) -> {System.out.println(s);};
func.accept("ラムダ式");
// 省略記法。上記と同じ処理。
Consumer<String> func2 = s -> System.out.println(s);
func2.accept("ラムダ式省略記法");
}
}
メソッド参照
メソッド参照はすでにあるメソッドを関数型インターフェースの抽象メソッドに突っ込む。
ここでは参照されるメソッド()がaccept()に突っ込まれている。
書き方
- staticメソッド:クラス名::メソッド名
- インスタンスメソッド:変数名::メソッド名
public class MethodSansyou implements Consumer<String>{
@Override
public void accept(String t) {}
static void 参照されるメソッド (String t) {
System.out.println(t);
}
public static void main(String[] args) {
Consumer<String> c = MethodSansyou::参照されるメソッド;
c.accept("メソッド参照");
}
}
やっていることは普通のも匿名クラスもラムダ式も全てacceptメソッドをオーバーライドしているだけです。
1、acceptメソッドをオーバーライド Consumer<String> func = (s) -> {System.out.println(s);};
2、acceptメソッド呼び出し func.accept("ラムダ式");
Stream APIで同じことをしてみる
Stream APIでラムダ式を使うと最強らしいのでやってみます。
先ほどと同様に以下の順番です。
・普通に実装
・匿名クラスで実装
・ラムダ式で実装
・メソッド参照で実装
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//...
}
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class Normal implements Consumer<String>{
@Override
public void accept(String s) {
System.out.println(s);
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
// forEachでlistの中身を取り出す。
Normal n = new Normal();
list.forEach(n);// forEachの引数にConsumer<String>型を渡す
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class Tokumei {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
}
import java.util.ArrayList;
import java.util.List;
public class Lambda4 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.forEach(s -> System.out.println(s));
}
}
ラムダ式だと非常に短い記述で済みますね!
implementsも@Overrideも書かなくてよいのでかなり楽です。
public class StreamMethodSansyou {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.forEach(System.out::println);
}
}
forEachの仕組み
forEachは引数にConsumer型を取ります。
forEach(Consumer<T> t)
forEachはacceptを繰り返し実行するメソッドです。
オーバーライドされたacceptが繰り返し実行されています。
メソッドの引数にメソッドを渡す
public class StreamMethodSansyou {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Consumer<Integer> func = s -> {
s = s * 2;
System.out.println(s);
};
list.forEach(func); //2 4 6
}
}
代表的な関数型インターフェース
関数型インターフェースは自分で書くこともできますが、よく使うものは既に用意されています。
Consumer
引数を受け取るだけで戻り値なし。
引数の型:T
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//...
}
Function
引数Tを受け取りRを返す。
引数の型:T
戻り値型:R
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
//...
}
Predicate
引数Tを受け取りbooleanを返す。
引数の型:T
戻り値型:boolean
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//...
}
Supplier
引数を受け取らずTを返す。
戻り値型:T
@FunctionalInterface
public interface Supplier<T> {
T get();
//...
}
まとめ
ラムダ式を使うとかなり記述を減らせることがわかりました。
普通 → 匿名クラス → ラムダ式 → メソッド参照という風に書き換えていくと慣れていけそうです。
配列やコレクション使用時には積極的に活用しようと思います。