Apakah ekspresi lambda membuat objek di heap setiap kali dieksekusi?

182

Ketika saya beralih koleksi menggunakan gula sintaksis baru Java 8, seperti

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

Bukankah ini setara dengan cuplikan 'sintaks lama' di bawah?

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

Apakah ini berarti Consumerobjek anonim baru dibuat di heap setiap kali saya mengulangi koleksi? Berapa banyak tumpukan ruang yang dibutuhkan? Apa implikasi kinerja yang dimilikinya? Apakah itu berarti saya lebih baik menggunakan gaya lama untuk loop ketika beralih pada struktur data multi-level yang besar?

Bastian Voigt
sumber
48
Jawaban singkat: tidak. Untuk lambdas yang tidak memiliki kewarganegaraan (mereka yang tidak menangkap apa pun dari konteks leksikal mereka), hanya satu contoh yang akan dibuat (dengan malas), dan di-cache di lokasi penangkapan. (Beginilah pelaksanaannya; spesifikasi ditulis dengan hati-hati untuk memungkinkan, tetapi tidak mengharuskan, pendekatan ini.)
Brian Goetz

Jawaban:

158

Ini setara tetapi tidak identik. Sederhananya, jika ekspresi lambda tidak menangkap nilai, itu akan menjadi singleton yang digunakan kembali pada setiap doa.

Perilaku tidak ditentukan secara spesifik. JVM diberi kebebasan besar tentang bagaimana mengimplementasikannya. Saat ini, JVM Oracle menciptakan (setidaknya) satu instance per ekspresi lambda (yaitu tidak berbagi instance antara ekspresi identik yang berbeda) tetapi membuat lajang untuk semua ekspresi yang tidak menangkap nilai.

Anda dapat membaca jawaban ini untuk lebih jelasnya. Di sana, saya tidak hanya memberikan deskripsi yang lebih rinci tetapi juga menguji kode untuk mengamati perilaku saat ini.


Ini dicakup oleh Spesifikasi Bahasa Java®, bab “ 15.27.4. Evaluasi Run-time tentang Ekspresi Lambda

Diringkas:

Aturan-aturan ini dimaksudkan untuk menawarkan fleksibilitas untuk implementasi bahasa pemrograman Java, dalam hal:

  • Objek baru tidak perlu dialokasikan pada setiap evaluasi.

  • Objek yang dihasilkan oleh ekspresi lambda yang berbeda tidak perlu milik kelas yang berbeda (jika badannya identik, misalnya).

  • Setiap objek yang dihasilkan oleh evaluasi tidak harus berasal dari kelas yang sama (misalnya, variabel lokal yang ditangkap dapat diuraikan, misalnya).

  • Jika "contoh yang ada" tersedia, itu tidak perlu dibuat pada evaluasi lambda sebelumnya (itu mungkin telah dialokasikan selama inisialisasi kelas lampiran, misalnya).

Holger
sumber
24

Ketika sebuah instance yang mewakili lambda dibuat secara sensitif tergantung pada isi yang tepat dari tubuh lambda Anda. Yaitu, faktor kuncinya adalah apa yang ditangkap lambda dari lingkungan leksikal. Jika tidak menangkap keadaan apa pun yang variabel dari kreasi ke kreasi, maka sebuah instance tidak akan dibuat setiap kali untuk setiap loop dimasukkan. Sebaliknya metode sintetis akan dihasilkan pada waktu kompilasi dan situs penggunaan lambda hanya akan menerima objek tunggal yang mendelegasikan metode tersebut.

Catatan lebih lanjut bahwa aspek ini tergantung pada implementasi dan Anda dapat mengharapkan perbaikan dan peningkatan di masa depan pada HotSpot menuju efisiensi yang lebih besar. Ada rencana umum untuk mis membuat objek ringan tanpa kelas yang sesuai penuh, yang hanya memiliki informasi yang cukup untuk meneruskan ke metode tunggal.

Berikut ini adalah artikel mendalam yang dapat diakses tentang topik ini:

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

Marko Topolnik
sumber
0

Anda memberikan contoh baru ke forEachmetode. Setiap kali Anda melakukannya, Anda membuat objek baru tetapi tidak satu untuk setiap iterasi loop. Iterasi dilakukan di dalam forEachmetode menggunakan instance objek 'callback' yang sama sampai selesai dengan loop.

Jadi memori yang digunakan oleh loop tidak tergantung pada ukuran koleksi.

Bukankah ini setara dengan cuplikan 'sintaks lama'?

Iya. Ini memiliki sedikit perbedaan pada tingkat yang sangat rendah tetapi saya tidak berpikir Anda harus peduli tentang mereka. Ekspresi Lamba menggunakan fitur invokedynamic daripada kelas anonim.

aalku
sumber
Apakah Anda memiliki dokumentasi yang menyebutkan ini? Ini optimasi yang cukup menarik.
A. Rama
Terima kasih, tetapi bagaimana jika saya memiliki koleksi koleksi koleksi, misalnya ketika melakukan pencarian mendalam-pertama pada struktur data pohon?
Bastian Voigt
2
@ A.Rama Maaf, saya tidak melihat optimasi. Itu sama dengan atau tanpa lambdas dan dengan atau tanpa loop masing-masing.
aalku
1
Ini tidak benar-benar sama, tetapi masih paling banyak satu objek per tingkat bersarang akan diperlukan pada satu waktu, yang dapat diabaikan. Setiap iterasi baru dari loop dalam akan membuat objek baru, kemungkinan besar menangkap item saat ini dari loop luar. Ini menciptakan beberapa tekanan GC, tetapi tetap tidak ada yang perlu dikhawatirkan.
Marko Topolnik
4
@ aalku: " Setiap kali Anda melakukannya, Anda membuat objek baru ": Tidak sesuai dengan jawaban Holger dan komentar ini oleh Brian Goetz .
Lii