Apakah referensi metode melewatkan overhead bungkus lambda? Mungkinkah mereka di masa depan?
Menurut Tutorial Java tentang Referensi Metode :
Terkadang ... ekspresi lambda tidak melakukan apa-apa selain memanggil metode yang ada. Dalam kasus tersebut, seringkali lebih jelas untuk merujuk pada metode yang ada dengan nama. Referensi metode memungkinkan Anda untuk melakukan ini; mereka adalah ekspresi lambda yang ringkas dan mudah dibaca untuk metode yang sudah memiliki nama.
Saya lebih suka sintaks lambda daripada sintaks referensi metode karena beberapa alasan:
Lambdas lebih jelas
Terlepas dari klaim Oracle, saya menemukan sintaks lambda lebih mudah dibaca daripada referensi metode objek secara langsung karena sintaks referensi metode bersifat ambigu:
Bar::foo
Apakah Anda memanggil metode satu argumen statis pada kelas x dan meneruskannya x?
x -> Bar.foo(x)
Atau apakah Anda memanggil metode instance argumen nol pada x?
x -> x.foo()
Sintaks referensi metode bisa bertahan untuk salah satu. Itu menyembunyikan apa yang sebenarnya dilakukan kode Anda.
Lambdas lebih aman
Jika Anda merujuk Bar :: foo sebagai metode kelas dan Bar kemudian menambahkan metode instance dengan nama yang sama (atau sebaliknya), kode Anda tidak akan lagi dikompilasi.
Anda dapat menggunakan lambdas secara konsisten
Anda dapat membungkus fungsi apa pun dalam lambda - sehingga Anda dapat menggunakan sintaksis yang sama secara konsisten di mana-mana. Sintaks referensi metode tidak akan bekerja pada metode yang mengambil atau mengembalikan array primitif, melempar pengecualian yang diperiksa, atau menggunakan nama metode yang sama digunakan sebagai instance dan metode statis (karena sintaks referensi metode tidak jelas tentang metode yang akan dipanggil) . Mereka tidak berfungsi ketika Anda memiliki metode yang berlebihan dengan jumlah argumen yang sama, tetapi Anda tidak harus tetap melakukannya (lihat item Josh Bloch 41) sehingga kami tidak dapat menentangnya terhadap referensi metode.
Kesimpulan
Jika tidak ada penalti kinerja untuk melakukannya, saya tergoda untuk mematikan peringatan di IDE saya dan menggunakan sintaks lambda secara konsisten tanpa menaburkan referensi metode sesekali ke dalam kode saya.
PS
Baik di sini maupun di sana, tetapi dalam mimpiku, referensi metode objek terlihat lebih seperti ini dan menerapkan invoke-dynamic terhadap metode langsung pada objek tanpa pembungkus lambda:
_.foo()
.map(_.acceptValue())
: Jika saya tidak salah, ini sangat mirip dengan sintaks Scala. Mungkin Anda hanya mencoba menggunakan bahasa yang salah.System.out::println
untukforEach()
sepanjang waktu ...?Jawaban:
Dalam banyak skenario, saya pikir lambda dan metode-referensi adalah sama. Tetapi lambda akan membungkus target doa dengan tipe antarmuka yang menyatakan.
Sebagai contoh
Anda akan melihat konsol menampilkan stacktrace.
Di
lambda()
, metode pemanggilantarget()
adalahlambda$lambda$0(InvokeTest.java:20)
, yang memiliki info saluran yang dapat dilacak. Jelas, itu adalah lambda yang Anda tulis, kompiler menghasilkan metode anonim untuk Anda. Dan kemudian, penelepon dari metode lambda adalah sesuatu sepertiInvokeTest$$Lambda$2/1617791695.run(Unknown Source)
, yaituinvokedynamic
panggilan di JVM, itu berarti panggilan tersebut terkait dengan metode yang dihasilkan .Dalam
methodReference()
, pemanggilan metodetarget()
secara langsungInvokeTest$$Lambda$1/758529971.run(Unknown Source)
, itu berarti panggilan tersebut secara langsung terkait denganInvokeTest::target
metode tersebut .Kesimpulan
Di atas semua itu, bandingkan dengan metode-referensi, menggunakan ekspresi lambda hanya akan menyebabkan satu lagi pemanggilan metode ke metode penghasil dari lambda.
sumber
Ini semua tentang metafactory
Pertama, sebagian besar metode referensi tidak perlu diinginkan oleh metafactory lambda, mereka hanya digunakan sebagai metode referensi. Di bawah bagian "Lambda body sugaring" artikel Terjemahan Lambda Expressions ("TLE"):
Ini lebih lanjut disorot lebih lanjut di TLE "The Lambda Metafactory":
Referensi
Integer::sum
metode statis ( ) atau tidak terikat (Integer::intValue
) adalah yang 'paling sederhana' atau yang paling 'nyaman', dalam arti bahwa mereka dapat ditangani secara optimal oleh varian metafactory 'jalur cepat' tanpa keinginan . Keunggulan ini sangat membantu ditunjukkan dalam "varian Metafactory" TLE:Tentu saja, referensi metode penangkapan-contoh (
obj::myMethod
) perlu menyediakan turunan terikat sebagai argumen pada pegangan metode untuk pemanggilan, yang dapat berarti kebutuhan untuk menggunakan metode 'jembatan'.Kesimpulan
Saya tidak begitu yakin apa yang dimaksud dengan 'pembungkus' lambda, tetapi meskipun hasil akhir dari menggunakan lambda atau referensi metode yang ditentukan pengguna adalah sama, cara yang dicapai tampaknya sangat berbeda, dan dapat berbeda di masa depan jika itu tidak terjadi sekarang. Oleh karena itu, saya kira itu lebih mungkin daripada tidak bahwa referensi metode dapat ditangani dengan cara yang lebih optimal oleh metafactory.
sumber
Ada satu konsekuensi yang cukup serius ketika menggunakan ekspresi lambda yang dapat mempengaruhi kinerja.
Ketika Anda mendeklarasikan ekspresi lambda, Anda membuat penutupan atas cakupan lokal.
Apa artinya ini dan bagaimana pengaruhnya terhadap kinerja?
Yah saya senang Anda bertanya. Ini berarti bahwa masing-masing ekspresi lambda kecil adalah kelas batin anonim kecil dan itu berarti disertai dengan referensi ke semua variabel yang berada dalam cakupan yang sama dari ekspresi lambda.
Ini berarti juga
this
referensi dari instance objek dan semua bidangnya. Bergantung pada waktu pemanggilan lambda yang sebenarnya, ini dapat menyebabkan kebocoran sumber daya yang cukup signifikan karena pengumpul sampah tidak dapat melepaskan referensi ini selama objek yang memegang lambda masih hidup ...sumber
this
objek yang bisa dirujuk. Oleh karena itu, lambda tidak membocorkan sumber daya hanya dengan memiliki ruang bagi mereka; hanya berpegang pada objek yang dibutuhkan. Di sisi lain, kelas dalam anonim dapat membocorkan sumber daya, tetapi tidak selalu melakukannya. Lihat kode ini sebagai contoh: a.blmq.us/2mmrL6v