ラムダ式は、実行されるたびにヒープ上にオブジェクトを作成しますか?


182

次のようなJava 8の新しい構文シュガーを使用してコレクションを反復処理すると、

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

これは、以下の「古い構文」スニペットと同等ではありませんか?

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

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


48
短い答え:いいえ。ステートレスラムダ(レキシカルコンテキストから何もキャプチャしないもの)の場合、1つのインスタンスのみが(遅延して)作成され、キャプチャサイトにキャッシュされます。(これが実装のしくみです。仕様は、このアプローチを許可するように(ただし必須ではない)慎重に記述されています。)
Brian Goetz 2014

回答:


158

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

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

あなたは読むことができるこの回答の詳細については。そこでは、より詳細な説明だけでなく、現在の動作を観察するためのテストコードも示しました。


これは、Java®言語仕様の「15.27.4。ラムダ式の実行時評価

要約:

これらのルールは、Javaプログラミング言語の実装に柔軟性を提供することを目的としています。

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

  • 異なるラムダ式によって生成されたオブジェクトは、異なるボディに属している必要はありません(ボディが同じ場合など)。

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

  • 「既存のインスタンス」が利用可能な場合、それは以前のラムダ評価で作成されている必要はありません(たとえば、それは囲んでいるクラスの初期化中に割り当てられた可能性があります)。


24

ラムダを表すインスタンスがいつ作成されるかは、ラムダの本体の正確な内容に依存します。つまり、重要な要素は、ラムダ語彙環境から取得するものです。作成ごとに変化する状態をキャプチャしない場合、for-eachループに入るたびにインスタンスは作成されません。代わりに、合成メソッドがコンパイル時に生成され、ラムダ使用サイトはそのメソッドにデリゲートするシングルトンオブジェクトを受け取るだけです。

さらに、この点は実装に依存するため、今後のHotSpotの改善と改善により、より高い効率が期待できます。たとえば、対応する完全なクラスなしで軽量オブジェクトを作成するという一般的な計画があります。これには、単一のメソッドに転送するのに十分な情報があります。

以下は、このトピックに関するアクセスしやすい詳細な記事です。

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood


0

forEachメソッドに新しいインスタンスを渡しています。そのたびに新しいオブジェクトを作成しますが、ループの反復ごとに1つとは限りません。forEachループで完了するまで、同じ「コールバック」オブジェクトインスタンスを使用してメソッド内で反復が行われます。

したがって、ループによって使用されるメモリは、コレクションのサイズに依存しません。

これは「古い構文」のスニペットと同等ではありませんか?

はい。非常に低いレベルでわずかな違いがありますが、あなたはそれらを気にする必要はないと思います。Lamba式は、匿名クラスではなく、invokedynamic機能を使用します。


これを指定するドキュメントはありますか?これは非常に興味深い最適化です。
A.ラマ

ありがとうございます。ただし、ツリーのデータ構造で深さ優先検索を実行する場合など、コレクションのコレクションがある場合はどうなりますか?
Bastian Voigt 2014

2
@ A.Rama申し訳ありませんが、最適化が表示されません。ラムダの有無にかかわらず、forEachループの有無にかかわらず同じです。
aalku 2014

1
それは実際には同じではありませんが、それでも、ネストレベルごとに最大で1つのオブジェクトが常に必要になるだけで、無視できます。内側のループの新しい繰り返しごとに新しいオブジェクトが作成され、おそらく外側のループから現在のアイテムがキャプチャされます。これにより、ある程度のGC圧力が生じますが、それでも実際に心配することはありません。
Marko Topolnik 2014

4
@aalku:「そのたびに新しいオブジェクトを作成します」:Holgerによるこの回答Brian Goetzによるこのコメントに従ってではありません。
Lii
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.