Lambda ifadesi, her yürütüldüğünde öbek üzerinde bir nesne oluşturur mu?


182

Java 8'in yeni sözdizimsel şekerini kullanarak bir koleksiyonu yinelediğimde,

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

Bu, aşağıdaki 'eski sözdizimi' snippet'ine eşdeğer değil mi?

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

Bu Consumer, bir koleksiyon üzerinde her yineleme yaptığımda öbek üzerinde yeni bir anonim nesne oluşturulduğu anlamına mı geliyor ? Bu ne kadar yığın alan kaplar? Performans üzerindeki etkileri nelerdir? Bu, büyük çok düzeyli veri yapıları üzerinde yineleme yaparken eski stili döngüler için kullanmayı tercih etmem anlamına mı geliyor?


48
Kısa cevap: hayır. Vatansız lambdalar (sözcüksel bağlamından hiçbir şey yakalamayanlar) için, yalnızca bir örnek (tembel olarak) oluşturulacak ve yakalama yerinde önbelleğe alınacaktır. (Uygulama bu şekilde çalışır; spesifikasyon bu yaklaşıma izin vermek, ancak gerektirmemek için dikkatlice yazılmıştır.)
Brian Goetz

Yanıtlar:


158

Eşdeğerdir ancak aynı değildir. Basitçe söylemek gerekirse, bir lambda ifadesi değerleri yakalamazsa, her çağrıda yeniden kullanılan tek bir ton olacaktır.

Davranış tam olarak belirtilmedi. JVM'ye nasıl uygulanacağı konusunda büyük bir özgürlük verilmektedir. Şu anda, Oracle'ın JVM'si lambda ifadesi başına (en az) bir örnek oluşturur (yani farklı özdeş ifadeler arasında örneği paylaşmaz), ancak değerleri yakalamayan tüm ifadeler için tektonlar oluşturur.

Daha fazla ayrıntı için bu cevabı okuyabilirsiniz . Orada, sadece daha ayrıntılı bir açıklama değil, aynı zamanda mevcut davranışı gözlemlemek için test kodu verdim.


Bu, Java® Dil Spesifikasyonu, “ 15.27.4. Lambda İfadelerinin Çalışma Zamanı Değerlendirmesi

özetlemiştir:

Bu kurallar, Java programlama dilinin uygulamalarına esneklik sağlamayı amaçlamaktadır:

  • Her değerlendirmede yeni bir nesne tahsis edilmesine gerek yoktur.

  • Farklı lambda ifadeleri tarafından üretilen nesnelerin farklı sınıflara ait olması gerekmez (örneğin, bedenler aynı ise).

  • Değerlendirmeyle üretilen her nesnenin aynı sınıfa ait olması gerekmez (örneğin yakalanan yerel değişkenler satır içi olabilir).

  • Bir "mevcut örnek" varsa, önceki bir lambda değerlendirmesinde yaratılmış olması gerekmez (örneğin, ekteki sınıfın başlatılması sırasında tahsis edilmiş olabilir).


24

Lambda'yı temsil eden bir örnek hassas olarak oluşturulduğunda, lambda vücudunuzun tam içeriğine bağlıdır. Yani, anahtar faktör lambda'nın sözcüksel ortamdan yakaladığı şeydir . Oluşturmadan yaratıma değişken olan herhangi bir durumu yakalamazsa, for-each döngüsüne her girildiğinde bir örnek oluşturulmaz. Bunun yerine derleme zamanında sentetik bir yöntem oluşturulacak ve lambda kullanım sitesi sadece bu yönteme yetki veren tek bir nesne alacaktır.

Ayrıca, bu yönün uygulamaya bağlı olduğunu ve HotSpot üzerinde daha fazla verimlilik için ileride yapılacak iyileştirmeler ve ilerlemeler bekleyebilirsiniz. Örneğin, tek bir yönteme iletmek için yeterli bilgiye sahip, tam karşılık gelen bir sınıfa sahip olmayan hafif bir nesne yapmak için genel planlar vardır.

İşte konuyla ilgili iyi, erişilebilir bir derinlemesine makale:

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


0

forEachYönteme yeni bir örnek geçiriyorsunuz. Bunu her yaptığınızda, her döngü yinelemesi için yeni bir nesne oluşturmazsınız. Yineleme, forEachdöngü içinde yapılana kadar aynı 'geri çağrı' nesne örneği kullanılarak yöntem içinde yapılır.

Bu yüzden döngü tarafından kullanılan bellek, koleksiyonun boyutuna bağlı değildir.

Bu, 'eski sözdizimi' snippet'ine eşdeğer değil mi?

Evet. Çok düşük düzeyde küçük farklılıkları var, ancak onları önemsemeniz gerektiğini düşünmüyorum. Lamba ifadeleri anonim sınıflar yerine invokinamik özelliği kullanır.


Bunu belirten belgeleriniz var mı? Bu oldukça ilginç bir optimizasyon.
A. Rama

Teşekkürler, ancak koleksiyon koleksiyonlarından oluşan bir koleksiyonum varsa, örneğin bir ağaç veri yapısında önce derinlemesine arama yaparken?
Bastian Voigt

2
@ A.Rama Üzgünüm, optimizasyonu göremiyorum. Lambdas ile veya lambdas olmadan ve forEach döngü ile veya olmadan aynıdır.
aalku

1
Gerçekten aynı değil, ama her seferinde yuvalama seviyesi başına en fazla bir nesneye ihtiyaç duyulacak, bu da ihmal edilebilir. Bir iç döngünün her yeni tekrarı, büyük olasılıkla geçerli öğeyi dış döngüden yakalayan yeni bir nesne oluşturur. Bu bir miktar GC basıncı yaratır, ancak yine de gerçekten endişelenecek bir şey yoktur.
Marko Topolnik

4
@aalku: " Bunu her yaptığınızda yeni bir nesne yaratırsınız ": Holger'in bu cevabına ve Brian Goetz'un bu yorumuna göre değil .
Lii
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.