LoginSignup
0
1

More than 3 years have passed since last update.

匿名クラス・ラムダ式・メソッド参照をほんのり理解する

Last updated at Posted at 2020-05-13

主にラムダ式について書きます。

ラムダ式は何ができるのか

メソッドを変数のように扱える。
メソッドの引数にメソッドを渡せる。
匿名クラスと同じことをより簡単な記述で書ける。

ラムダ式のお決まり事

関数型インターフェースの抽象メソッドをオーバーライドする必要がある。
※関数型インターフェース = 1つだけ抽象メソッドを持つインターフェース。

基本の書き方

(型 引数名) ー> {処理1;};

(Object o) -> {System.out.println(o);};

普通 → 匿名クラス → ラムダ式 → メソッド参照の順番に書いてみる

ラムダ式は実は抽象メソッドをオーバーライドしているだけなのです。
それを理解するために
・普通にオーバーライド
・匿名クラスでオーバーライド
・ラムダ式でオーバーライド
・メソッド参照
という4つで書いてみます。

今回は関数型インターフェースConsumerの抽象メソッドacceptをオーバーライドして実装していきます。

普通

Consumerインターフェース
@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("ラムダ式省略記法");
            }
}

メソッド参照

・参考
【Java】ラムダ式、メソッド参照

メソッド参照はすでにあるメソッドを関数型インターフェースの抽象メソッドに突っ込む。
ここでは参照されるメソッド()が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でラムダ式を使うと最強らしいのでやってみます。

先ほどと同様に以下の順番です。
・普通に実装
・匿名クラスで実装
・ラムダ式で実装
・メソッド参照で実装

Consumerインターフェース
@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();
//...
}

まとめ

ラムダ式を使うとかなり記述を減らせることがわかりました。
普通 → 匿名クラス → ラムダ式 → メソッド参照という風に書き換えていくと慣れていけそうです。
配列やコレクション使用時には積極的に活用しようと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1