かきあげせいろはラーメンがたべたい

技術プログラミングいろいろ記録メモ備忘にゃーん!

匿名クラス&ラムダ式

社内勉強会の自分用メモです。

匿名クラス

一回しか利用しないクラスを定義&インスタンス作成します。

public static void main(String[] args) {

        // ️Hogeインターフェースを実装した匿名クラスのインスタンスを作成
        Hoge anonymous = new Hoge() {
            @Override
            public void print() {
                System.out.println("TEST1");
            }
        };
        anonymous.print();

    }

    public interface Hoge {
        void print();
}

出力結果:TEST1

ラムダ式とは

匿名クラスを簡単に書ける記法のようなもの

package console;

public class Test {
    public static void main(String[] args) {
        // ラムダ式
        Hoge lambda = () -> System.out.println("TEST1");
        lambda.print();
    }

    public interface Hoge {
        void print();
    }
}

出力結果:TEST1

上記のように、匿名クラスのコードと同様の結果になります。
そのため、eclipseではクイックフィックス機能を利用することで相互に変換できます。

f:id:KakiageSeiro:20190216235756p:plain f:id:KakiageSeiro:20190216235800p:plain

匿名クラスを理解できていると、ラムダ式がどのような動きをしているか理解しやすいのでセットで覚えたいですね。

コールバックとは

以下のコードの出力はどのようになるでしょうか?

public class Test {
    public static void main(String[] args) {
        
        System.out.println("TEST_A");
        
        // ラムダ式
        Hoge lambda = () -> System.out.println("TEST_B");
        
        System.out.println("TEST_C");
        
        lambda.print();
    }

    public interface Hoge {
        void print();
    }
}

実行すると、A→C→Bの順に出力されます。
これはBの実行がlambda.print();の時点で行われるためです。

以下のコードでも実行結果はA→C→B2となります。

public class Test {
    public static void main(String[] args) {

        System.out.println("TEST_A");

        // ラムダ式
        // Hoge lambda = () -> System.out.println("TEST_B");
        Hoge hogeImpl = new Hoge();

        System.out.println("TEST_C");

        // lambda.print();
        hogeImpl.print();
    }

    public interface Hoge {
        void print();
    }

}

class HogeImpl implements Test.Hoge {
    public HogeImpl() {
    }

    public void print() {
        System.out.println("TEST_B2");
    }
}

両者の違いは、匿名クラス(ラムダ式)でインスタンスを作成しているか、通常のクラス定義をしているかですが、この違いは"System.out.println("TEST_B")を記載する場所の違い"でもあります。

  • ラムダ式の場合     = printメソッドの呼び出し元で記載
  • 通常のクラス定義の場合 = printメソッドの呼び出し先で記載

前置きが長くなってしまいましたが、コールバックとは呼び出し元で記載した処理(関数オブジェクト)を別の処理内で動作させるための方法のようなものです。
ラムダ式や匿名クラスは、「インターフェースに定義したメソッドの実装を呼び出し元で定義&1回限り利用するクラス定義→インスタンス作成」することができます。

Optional(null安全)

データはgetした際に、データが取得できることが保証されていないリソースの場合、getの結果がnullであることが想定されます。

そのため、取得したデータがnullではないことをチェックする必要があります。

public class Test {
    public static void main(String[] args) {
        String hoge = getHoge();
        
        //nullだった場合は処理しない
        if(hoge == null) {
            return;
        }
    }
    
    static String getHoge() {
        return null;
    }
}

上記のように取得後にnullチェックをすることを徹底すればエラーは発生しません。が、人間がコーディングする以上ミスはありえます。

そこで、以下のようにOptional型でラップした型(ここではString)を返却するようにします。

import java.util.Optional;

public class Test {
    public static void main(String[] args) {
        String hoge = getHoge(); //ここでコンパイルエラー
        
        //nullだった場合は処理しない
        if(hoge == null) {
            return;
        }
    }
    
    //返却する型をOptionalでラップする
    static Optional<String> getHoge() {
        return null;
    }
}

こうすることで、mainの呼び出し部分で型が合わないためコンパイルエラーになります。

したがって呼び出し側は、getHogeを利用する際に以下のように呼び出すことを強制されます。

public class Test {
    public static void main(String[] args) {
        Optional<String> hoge = getHoge(); //Optional<String>で結果を受け取る
        
        //hogeがnullではない場合、コールバック関数を実行するOptional.ifPresentを利用し、アンラップする。
        hoge.ifPresent(h -> System.out.println(h));
    }
    
    static Optional<String> getHoge() {
        //Optional.ofNullable(null);
        return Optional.ofNullable("nullではない");
    }
}

実行結果:nullではない

Optional.ifPresentはhogeがnullではない場合のみ、引数のコールバック関数を実行するメソッドです。

このように、呼び出し側にnullチェックを強制させることで、実行時にぬるぽでアプリケーションが落ちることを防止できます(コンパイル時にエラーに気づける)。

ちなみに、上記でgetHogeからOptional.ofNullable(null)が返却された場合、実行結果は何もありません。(hoge.ifPresentで引数のコールバック関数が実行されない)