ラムダのオブジェクトはどう作られるか

ラムダは匿名クラスのシンタックスシュガーだとか言われますけど、オブジェクトの作られ方が違いますね。
というのを検証してみます。
(jdk1.8.0_112で動かしています)


ループの中でhogeにラムダを渡して、hogeでオブジェクトを表示します。
まず匿名クラスを使ったコード。

public class LambdaObj {
    public static void main(String[] args) {
        for (int i = 0; i < 2; ++i) {
            hoge(new Runnable() {
                @Override
                public void run() {
                }
            });
        }
    }
    
    static void hoge(Runnable r) {
        System.out.println(r);
    }
}


実行するとこう。

myproject.LambdaObj$1@15db9742
myproject.LambdaObj$1@6d06d69c

明確にnewしてるので、毎回違うオブジェクトが渡されています。


これをラムダにしてみます。

public class LambdaObj {
    public static void main(String[] args) {
        for (int i = 0; i < 2; ++i) {
            hoge(() -> {});
        }
    }
    
    static void hoge(Runnable r) {
        System.out.println(r);
    }
}


実行すると、こう。

myproject.LambdaObj$$Lambda$1/834600351@548c4f57
myproject.LambdaObj$$Lambda$1/834600351@548c4f57

オブジェクトが使いまわされていることがわかります。
ラムダと匿名クラスは違うということですね。
バイトコードとしては

private static void lambda$main$0() {
  //ラムダの中身
}

のようなメソッドが生成されて、invokeDynamicをよんでラムダオブジェクトを取ってくるようなコードが生成されます。このinvokeDynamicでいろいろやられているということですね。


めでたしめでたし。


ではなくて、もうちょっと調べてみます。
ラムダの中で、外側のローカル変数であるargsを使ってみます。

public class LambdaObj {
    public static void main(String[] args) {
        for (int i = 0; i < 2; ++i) {
            hoge(() -> {
                String[] a = args;
            });
        }
    }
    
    static void hoge(Runnable r) {
        System.out.println(r);
    }
}


そうすると、こう。

myproject.LambdaObj$$Lambda$1/834600351@1fb3ebeb
myproject.LambdaObj$$Lambda$1/834600351@548c4f57


この場合は匿名クラスに展開されるのかなーと思ったけど

private static void lambda$main$0(String[]) {
  //ラムダの中身
}

のようなメソッドが定義されますね。


外側のローカル変数を使わない場合は簡易なオブジェクトを使っていて、外側のローカル変数を使う場合は引き渡すローカル変数のコピーをもったオブジェクトを渡さないといけないので毎回オブジェクトを生成してる、とかですかね。