Java 8'de lambda sözdizimi yerine yöntem başvuru sözdizimini kullanmanın bir performans avantajı var mı?


56

Yöntem referansları lambda sargısının üst kısmını atlıyor mu? Gelecekte olabilirler mi?

Java Eğitim Kılavuzuna göre, Referanslar :

Bazen ... bir lambda ifadesi var olan yöntemi çağırmaktan başka bir şey yapmaz. Bu gibi durumlarda, mevcut yönteme ada göre başvurmak genellikle daha açıktır. Yöntem başvuruları bunu yapmanızı sağlar; Zaten bir adı olan yöntemler için kompakt, okunması kolay lambda ifadeleridir.

Lambda sözdizimini birkaç nedenden dolayı yöntem referans sözdizimine tercih ederim:

Lambdalar daha net

Oracle'ın iddialarına rağmen, lambda sözdizimini, yöntem başvurusu sözdiziminin belirsiz olması nedeniyle, kısa yoldan nesne yöntemi referansından daha kolay okunur buluyorum:

Bar::foo

X sınıfı üzerinde statik bir bağımsız değişken yöntemi mi çağırıyorsunuz ve x'i geçiyorsunuz?

x -> Bar.foo(x)

Yoksa x'te bir sıfır bağımsız değişken örneği yöntemi mi çağırıyorsunuz?

x -> x.foo()

Yöntem referansı sözdizimi, her ikisinde de bulunabilir. Kodunuzun gerçekte ne yaptığını gizler.

Lambdalar daha güvenlidir

Bar :: foo'ya bir sınıf yöntemi olarak başvurduysanız ve Bar daha sonra aynı adı taşıyan bir örnek yöntemi eklerse (veya tersi), kodunuz artık derlenmez.

Lambdaları sürekli olarak kullanabilirsiniz.

Herhangi bir işlevi bir lambdaya sarabilirsiniz - böylece aynı sözdizimini her yerde tutarlı bir şekilde kullanabilirsiniz. Yöntem referansı sözdizimi, ilkel dizileri alan ya da döndüren, kontrol edilen istisnalar atan ya da örnek ve statik bir yöntemle aynı yöntem adına sahip olan (çalışma yöntemi sözdiziminin hangi yöntemin çağrılacağı konusunda belirsiz olduğu için) aynı yöntem adına sahip olmayacak . Aynı sayıda argümanla aşırı yükleme yaptığınız zaman çalışmazlar, ancak yine de yapmamalısınız (bkz. Josh Bloch'un madde 41'i), bu yüzden metot referanslarına aykırı olamayız.

Sonuç

Bunu yapmak için herhangi bir performans cezası yoksa, IDE'mdeki uyarıyı kapatmaya çalışıyorum ve lambda sözdizimini, arada sırada olan yöntem referansını koduma sermeden kullanıyorum.

PS

Ne burada ne de orada, ama rüyamda, nesne yöntemi referansları daha çok benziyor ve lambda sargısı olmadan doğrudan nesne üzerinde yönteme karşı invoke-dinamik uygular:

_.foo()

2
Bu, sorunuza cevap vermiyor, ancak kapak kullanmanın başka nedenleri de var. Bence, birçoğu ek bir işlev çağrısının minik yükünden daha ağır basar.

9
Bence erken performans için endişeleniyorsun. Benim düşünceme göre performans bir yöntem referansı kullanmak için ikincil bir husus olmalıdır; hepsi niyetini ifade etmekle ilgili. Bir işlevi bir yere aktarmanız gerekirse , ona atanan başka bir işlev yerine işlevi neden geçmiyorsunuz ? Bana tuhaf geliyor; İşlevlerin birinci sınıf şeyler olduğunu tam anlamıyla satın almadığınız gibi, dolaşmak için adsız bir şaperona ihtiyaçları var. Ancak sorunuzu daha doğrudan ele almak için, bir yöntem referansının statik bir çağrı olarak uygulanabilmesi şaşırtıcı olmaz.
Doval

7
.map(_.acceptValue()): Yanılmıyorsam, bu Scala sözdizimine çok benziyor. Belki de yanlış dili kullanmaya çalışıyorsun.
Giorgio

2
"Yöntem başvuru sözdizimi, void döndüren yöntemlerde çalışmaz" - Nasıl yani? Ben geçiyorum System.out::printlniçin forEach()... her zaman?
Lukas Eder

3
“Yöntem referansı sözdizimi, ilkel dizileri alan ya da döndüren, kontrol edilen istisnaları atan yöntemler üzerinde çalışmaz…” Bu doğru değil. Söz konusu işlevsel arabirim izin verdiği sürece, bu gibi durumlar için yöntem referanslarını ve lambda ifadelerini kullanabilirsiniz.
Stuart Marks

Yanıtlar:


12

Birçok senaryoda, lambda ve yöntem referansının eşdeğer olduğunu düşünüyorum. Ancak lambda, aşılama hedefini bildiren arayüz tipine göre saracaktır.

Örneğin

public class InvokeTest {

    private static void invoke(final Runnable r) {
        r.run();
    }

    private static void target() {
        new Exception().printStackTrace();
    }

    @Test
    public void lambda() throws Exception {
        invoke(() -> target());
    }

    @Test
    public void methodReference() throws Exception {
        invoke(InvokeTest::target);
    }
}

Konsolun yığma çıktısını göreceksiniz.

Olarak lambda(), yöntem çağıran target()olduğunu lambda$lambda$0(InvokeTest.java:20), izlenebilir çizgi bilgisi olan. Açıkçası, bu yazdığınız lambda, derleyici sizin için isimsiz bir yöntem oluşturur. Ve sonra, lambda yönteminin arayanı gibi bir şeydir InvokeTest$$Lambda$2/1617791695.run(Unknown Source), yani invokedynamicJVM'deki çağrı, çağrı üretilen yöntemle bağlantılı demektir .

İçinde methodReference(), yöntem çağrısının target()doğrudan yapılması InvokeTest$$Lambda$1/758529971.run(Unknown Source), çağrının yöntemin doğrudan bağlantılıInvokeTest::target olması anlamına gelir .

Sonuç

Hepsinden önemlisi, lambda ifadesinin kullanılması, lambda'dan üretim yöntemine yalnızca bir yöntem daha çağrılmasına neden olacak şekilde, yöntem referansıyla karşılaştırılır .


1
Aşağıdaki metafactory hakkında cevap biraz daha iyidir. Bununla ilgili bazı yararlı yorumlar ekledim (yani, Oracle / OpenJDK gibi uygulamaların, lambda / method işleci örnekleri oluşturmak için gereken sarıcı sınıf nesnelerini oluşturmak için ASM'yi nasıl kullandıklarını.
Ajax,

29

Her şey metafaktör hakkında

İlk olarak, çoğu yöntem referansları lambda metafaktör tarafından sökülmeye ihtiyaç duymaz, sadece referans yöntemi olarak kullanılırlar. Lambda İfadeleri Çevirisi ("TLE") makalesinin "Lambda vücut şekeri" bölümü altında :

Her şey eşit, özel yöntemler tercih edilen özel, statik yöntemler, örnek yöntemlere göre tercih edilir, en iyisi, lambda gövdelerinin lambda ifadesinin göründüğü en içteki sınıfa çekilmesi, imzaların lambda'nın vücut imzası ile eşleşmesi gerektiğidir. Argümanlar, yakalanan değerler için argüman listesinin önünde hazırlanmalı ve metot referanslarını hiç bir zaman bırakmayacaktır. Ancak, bu temel stratejiden sapmak zorunda kalabileceğimiz istisna durumlar vardır.

Bu, TLE'nin "The Lambda Metafactory" bölümünde daha da vurgulanmıştır:

metaFactory(MethodHandles.Lookup caller, // provided by VM
            String invokedName,          // provided by VM
            MethodType invokedType,      // provided by VM
            MethodHandle descriptor,     // lambda descriptor
            MethodHandle impl)           // lambda body

implBağımsız değişken lambda yöntem, şekeri alınmış lambda gövde ya da bir yöntem referans olarak adlandırılan her iki yöntemi tanımlar.

Statik ( Integer::sum) veya sınırlandırılmamış bir örnek yöntemi ( Integer::intValue) referansları, 'hızlı-yol' metafaktör varyantı tarafından sıyırma işlemi olmadan en iyi şekilde ele alınabilmeleri anlamında, 'en basit' veya en 'uygun' referanslardır . Bu avantaj, TLE’nin “Metafactory varyantlarında” yardımcı olarak belirtilmiştir:

Gerekli olmadıkları argümanları ortadan kaldırarak, sınıf dosyaları küçülür. Ve hızlı yol seçeneği, VM'nin lambda dönüşüm işleminin içselleştirmesini sağlayarak çıtayı düşürerek, bir "boks" işlemi olarak işlem görmesini ve kutudan çıkarma optimizasyonlarını kolaylaştırmasını sağlar.

Doğal olarak, bir örnek yakalama yöntemi reference ( obj::myMethod) sınırlandırılmış örneğe, çağrı için yöntem tutamacının bir argümanı olarak sunulması gerekir; bu, 'köprü' yöntemlerini kullanarak geri çekilme ihtiyacı anlamına gelebilir.

Sonuç

İddia ettiğiniz lamda 'sarmalayıcısının' ne olduğundan tam olarak emin değilim, ancak kullanıcı tanımlı lambda veya yöntem referanslarınızı kullanmanın nihai sonucu aynı olsa da , ulaşılan yol oldukça farklı görünüyor ve Şimdi durum böyle değilse gelecekte farklı olabilir. Bu nedenle, yöntem referanslarının metafactory tarafından daha uygun bir şekilde ele alınmasının mümkün olmamasından daha muhtemel olduğunu düşünüyorum.


1
Bu en alakalı cevap, aslında bir lambda oluşturmak için gereken iç makinelere değdiği için. Gerçekten lambda oluşturma işleminde hata ayıklayan biri olarak (belirli bir lambda / yöntem referansı için kararlı id'lerin nasıl üretileceğini bulmak için, haritalardan nasıl çıkarılabileceğini bulmak için), bunu oluşturmak için ne kadar iş yapıldığını oldukça şaşırtıcı buldum. bir lambda örneği. Oracle / OpenJDK, gerektiğinde, lambda'nızda referans verilen herhangi bir değişkeni (veya bir yöntem referansının örnek niteleyicisini) kapatmak için sınıfları dinamik olarak oluşturmak için ASM'yi kullanır ...
Ajax

1
... Değişkenleri kapatmanın yanı sıra, özellikle de lambdalar ayrıca bildiren sınıfa enjekte edilen statik bir yöntem oluşturur, böylece oluşturulan lambda çağırmak istediğiniz işlev (ler) in eşlemek için çağıracak bir şeye sahiptir. Örnek niteleyicisine sahip bir yöntem başvurusu, bu örneği bu yönteme parametre olarak alır; Özel bir sınıftaki this :: method referansının bu kapatmayı geçip geçmediğini test etmedim, ancak statik yöntemlerin aslında ara yöntemi atladığını test ettim (ve yalnızca adresindeki başlatma hedefine uymak için bir nesne oluşturun) çağrı sitesi).
Ajax,

1
Muhtemelen özel bir yöntemin de sanal gönderime ihtiyacı olmaz, oysa daha fazla açık olan herhangi bir şeyin örneği (kapatılmış bir statik yöntemle) kapatması gerekir, böylece geçersiz kıldığından emin olmak için asıl örnek üzerinde istilacı olabilir. TL; DR: Yöntem referansları daha az argüman üzerinde kapanıyor, bu yüzden daha az sızdırıyor ve daha az dinamik kod oluşturmayı gerektiriyor.
Ajax,

3
Son spam yorumları ... Dinamik sınıf üretimi oldukça ağır. Yine de sınıf yükleyicisine anonim bir sınıf yüklemekten daha iyidir (en ağır kısımlar yalnızca bir kez yapıldığından), ancak lambda örneği oluşturmak için ne kadar çalışmanın gerçekleştiğine gerçekten şaşıracaksınız. Anonim sınıflara karşı yöntem referanslarına karşı lambdaların gerçek ölçütlerini görmek ilginç olurdu. Aynı şeyleri referans alan iki lambda veya yöntem referansının, ancak farklı yerlerde çalışma zamanında farklı sınıflar oluşturduğunu unutmayın (aynı yöntem içinde bile, birbiri ardına).
Ajax,


0

Performansı etkileyebilecek lambda ifadeleri kullanıldığında oldukça ciddi bir sonuç vardır.

Bir lambda ifadesi bildirdiğinizde , yerel kapsamda bir kapanma yaratıyorsunuzdur .

Bu ne anlama geliyor ve performansı nasıl etkiliyor?

Peki sorduğuna sevindim. Bu küçük lambda ifadelerinin her birinin küçük bir anonim iç sınıf olduğu ve lambda ifadesinin aynı kapsamındaki tüm değişkenlere referans yaptığı anlamına gelir.

Bu aynı zamanda thisnesne örneğinin ve tüm alanlarının referansı anlamına gelir . Lambda'nın gerçek çağırma zamanlamasına bağlı olarak, bu, çöp toplayıcı bir lambda tutan nesne hala hayatta olduğu sürece, bu referanslardan vazgeçemediğinden, oldukça önemli bir kaynak sızıntısı olabilir.


Aynı statik olmayan yöntem referansları için de geçerli.
Ajax,

12
Java lambdaları kesinlikle kapanmaz. Ne de anonim iç sınıflar. Bildirildikleri kapsamdaki tüm değişkenlere referans taşımazlar. SADECE gerçekten referans aldıkları değişkenlere bir referans taşırlar. Bu aynı zamanda geçerli olduğu thisnesne olabilir başvurulan. Bir lambda, bu nedenle, sadece kendileri için bir alan bulundurarak kaynakları sızdırmaz; sadece ihtiyaç duyduğu cisimleri tutar. Öte yandan, isimsiz bir iç sınıf kaynakları sızdırabilir, ancak bunu her zaman yapmaz. Örnek için bu koda bakın: a.blmq.us/2mmrL6v
squid314

Bu cevap sadece yanlıştır
Stefan Reich

Teşekkürler @StefanReich, Bu çok yardımcı oldu!
Roland Tepp
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.