1. ホーム

[解決済み】ラムダ式は、実行されるたびにヒープ上にオブジェクトを作成するのか?

2022-04-13 15:57:31

質問

Java 8 の新しい構文シュガーを使ってコレクションを反復処理する場合、次のようになります。

myStream.forEach(item -> {
  // do something useful
});

これは、以下の「古い構文」のスニペットと同等ではないでしょうか?

myStream.forEach(new Consumer<Item>() {
  @Override
  public void accept(Item item) {
    // do something useful
  }
});

これは、新しい匿名 Consumer オブジェクトは、コレクションを反復処理するたびにヒープ上に作成されるのですか?これにはどれくらいのヒープスペースが必要ですか?また、パフォーマンスにはどのような影響があるのでしょうか?大きなマルチレベルのデータ構造を反復処理するときは、むしろ古いスタイルのforループを使うべきということでしょうか?

どのように解決するのですか?

同等ではありますが、同一ではありません。簡単に言うと、ラムダ式が値を取り込まない場合、呼び出しのたびに再利用されるシングルトンになる。

動作は厳密に指定されているわけではありません。JVMはそれをどのように実装するかについて、大きな自由を与えられています。現在、OracleのJVMは、ラムダ式ごとに(少なくとも)1つのインスタンスを作成しますが(つまり、異なる同一の式間でインスタンスを共有しません)、値をキャプチャしないすべての式に対してシングルトンを作成します。

以下はその例です。 この回答 をご覧ください。そこでは、より詳細な説明だけでなく、現在の挙動を観察するためのテストコードも示しています。


これは、The Java® Language Specificationの章 " 15.27.4. ラムダ式の実行時評価 "

まとめました。

このルールは、プログラミング言語Javaの実装に柔軟性を提供することを意図している、という点です。

  • 評価のたびに新しいオブジェクトを割り当てる必要はありません。

  • 異なるラムダ式によって生成されたオブジェクトは、異なるクラスに属する必要はありません (例えば、ボディが同一の場合)。

  • 評価によって生成されるすべてのオブジェクトは、同じクラスに属する必要はありません(たとえば、取り込まれたローカル変数がインライン化されるかもしれません)。

  • 既存のインスタンスがある場合、以前のラムダ評価で作成されたものである必要はありません(例えば、囲んだクラスの初期化時に割り当てられたものかもしれません)。