जावा 8 में लैम्ब्डा सिंटैक्स के बजाय विधि संदर्भ सिंटैक्स का उपयोग करने के लिए एक प्रदर्शन लाभ है?


56

विधि संदर्भ लंबर आवरण के ओवरहेड को छोड़ दें? क्या वे भविष्य में हो सकते हैं?

विधि संदर्भों पर जावा ट्यूटोरियल के अनुसार :

कभी-कभी ... एक लंबोदर अभिव्यक्ति एक मौजूदा पद्धति के अलावा कुछ नहीं करती है। उन मामलों में, मौजूदा विधि को नाम से संदर्भित करना अक्सर स्पष्ट होता है। विधि संदर्भ आपको ऐसा करने में सक्षम करते हैं; वे उन विधियों के लिए कॉम्पैक्ट, आसानी से पढ़े जाने वाले लैम्बडा एक्सप्रेशन हैं जिनका पहले से ही एक नाम है।

मैं लैंबडा सिंटैक्स को कई कारणों से विधि संदर्भ सिंटैक्स के लिए पसंद करता हूं:

लंबोदर स्पष्ट हैं

ओरेकल के दावों के बावजूद, मुझे लैम्बडा सिंटैक्स आसानी से पढ़ने के लिए ऑब्जेक्ट विधि संदर्भ शॉर्ट-हैंड से मिलता है क्योंकि विधि संदर्भ सिंटैक्स अस्पष्ट है:

Bar::foo

क्या आप x के वर्ग पर एक स्थिर एक-तर्क पद्धति को बुला रहे हैं और इसे x पास कर रहे हैं?

x -> Bar.foo(x)

या आप x पर शून्य-तर्क उदाहरण विधि कह रहे हैं?

x -> x.foo()

विधि संदर्भ सिंटैक्स किसी एक के लिए भी खड़ा हो सकता है। यह छुपाता है कि आपका कोड वास्तव में क्या कर रहा है।

लंबोदर सुरक्षित हैं

यदि आप बार का संदर्भ देते हैं :: वर्ग विधि के रूप में फू और बार बाद में उसी नाम (या इसके विपरीत) की एक आवृत्ति विधि जोड़ता है, तो आपका कोड अब संकलित नहीं होगा।

आप लैम्ब्डा का लगातार उपयोग कर सकते हैं

आप किसी भी फ़ंक्शन को लैम्ब्डा में लपेट सकते हैं - इसलिए आप हर जगह एक ही वाक्यविन्यास का लगातार उपयोग कर सकते हैं। विधि संदर्भ वाक्यविन्यास उन तरीकों पर काम नहीं करेगा जो आदिम सरणियों को लेते या वापस करते हैं, जाँच किए गए अपवादों को फेंकते हैं, या एक ही विधि का नाम उदाहरण और स्थिर विधि के रूप में उपयोग किया जाता है (क्योंकि विधि संदर्भ वाक्यविन्यास अस्पष्ट है कि किस विधि को कहा जाएगा) । जब आप एक ही नंबर के तर्कों के साथ तरीकों को ओवरलोड कर लेते हैं, तो वे काम नहीं करते हैं, लेकिन आपको वैसे भी ऐसा नहीं करना चाहिए (जोश बलोच के आइटम 41 देखें) ताकि हम इसे विधि संदर्भ के विरुद्ध न पकड़ सकें।

निष्कर्ष

यदि ऐसा करने के लिए कोई प्रदर्शन दंड नहीं है, तो मुझे अपनी आईडीई में चेतावनी को बंद करने का प्रलोभन दिया जाता है और मेरे कोड में सामयिक पद्धति के संदर्भ को छिड़कने के बिना लैम्ब्डा सिंटैक्स का लगातार उपयोग किया जाता है।

पुनश्च

न तो यहां और न ही, लेकिन मेरे सपनों में, ऑब्जेक्ट विधि संदर्भ इस तरह दिखते हैं और एक लैम्ब्डा आवरण के बिना ऑब्जेक्ट पर सीधे विधि के खिलाफ इनवोक-डायनेमिक लागू करते हैं:

_.foo()

2
यह आपके प्रश्न का उत्तर नहीं देता है, लेकिन क्लोजर का उपयोग करने के अन्य कारण हैं। मेरी राय में, उनमें से कई अब तक एक अतिरिक्त फ़ंक्शन कॉल के छोटे ओवरहेड से आगे निकल गए हैं।

9
मुझे लगता है कि आप समय से पहले प्रदर्शन के बारे में चिंता कर रहे हैं। मेरी राय में प्रदर्शन एक विधि संदर्भ का उपयोग करने के लिए एक माध्यमिक विचार होना चाहिए; यह आपके इरादे को व्यक्त करने के बारे में है। यदि आपको कहीं पर एक समारोह पास करने की आवश्यकता है, तो केवल कुछ अन्य फ़ंक्शन के बजाय फ़ंक्शन को पास क्यों करें जो इसे दर्शाता है? यह मुझे अजीब लगता है; जैसे आप काफी नहीं खरीदते हैं कि फ़ंक्शंस प्रथम श्रेणी की चीजें हैं, उन्हें स्थानांतरित करने के लिए एक अनाम चैपरोन की आवश्यकता होती है। लेकिन आपके प्रश्न को अधिक सीधे संबोधित करने के लिए, मुझे आश्चर्य नहीं होगा कि क्या विधि संदर्भ को एक स्थिर कॉल के रूप में लागू किया जा सकता है।
डोभाल

7
.map(_.acceptValue()): अगर मैं गलत नहीं हूं, तो यह बहुत हद तक स्काला सिंटैक्स की तरह दिखता है। हो सकता है कि आप सिर्फ गलत भाषा का उपयोग करने की कोशिश कर रहे हों।
जियोर्जियो

2
"विधि संदर्भ सिंटैक्स शून्य वापस करने वाले तरीकों पर काम नहीं करेगा" - ऐसा कैसे? मैं गुजर रहा हूँ System.out::printlnकरने के लिए forEach()हर समय ...?
लुकास एडर

3
"विधि संदर्भ वाक्यविन्यास उन विधियों पर काम नहीं करेगा जो आदिम सरणियों को लेते या वापस करते हैं, जाँच किए गए अपवादों को फेंकते हैं ...." यह सही नहीं है। आप ऐसे मामलों के लिए विधि संदर्भों के साथ-साथ लंबोदर अभिव्यक्तियों का उपयोग कर सकते हैं, जब तक कि प्रश्न में कार्यात्मक इंटरफ़ेस इसे अनुमति देता है।
स्टुअर्ट मार्क्स

जवाबों:


12

कई परिदृश्यों में, मुझे लगता है कि लैम्ब्डा और विधि-संदर्भ बराबर है। लेकिन लैम्ब्डा घोषित इंटरफेस प्रकार द्वारा आह्वान लक्ष्य को लपेटेगा।

उदाहरण के लिए

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);
    }
}

आप कंसोल आउटपुट को स्टैकट्रेस देखेंगे।

में lambda(), कॉलिंग विधि target()है lambda$lambda$0(InvokeTest.java:20), जिसमें ट्रेस करने योग्य लाइन जानकारी है। जाहिर है, यह आपके द्वारा लिखा गया लंबोदर है, संकलक आपके लिए एक अनाम विधि तैयार करता है। और फिर, लैम्ब्डा विधि का कॉलर कुछ इस तरह है InvokeTest$$Lambda$2/1617791695.run(Unknown Source), कि invokedynamicजेवीएम में कॉल है, इसका मतलब है कि कॉल उत्पन्न विधि से जुड़ा हुआ है

में methodReference(), कॉलिंग मेथड target()सीधे है InvokeTest$$Lambda$1/758529971.run(Unknown Source), इसका मतलब है कि कॉल मेथड से सीधे जुड़ा हुआ हैInvokeTest::target

निष्कर्ष

इन सबसे ऊपर, विधि-संदर्भ की तुलना करें, लैम्ब्डा अभिव्यक्ति का उपयोग केवल लैम्बडा से उत्पन्न विधि के लिए एक और विधि कॉल का कारण होगा ।


1
मेटाफ़ैक्ट के बारे में नीचे दिया गया उत्तर थोड़ा बेहतर है। मैंने कुछ उपयोगी टिप्पणियाँ जोड़ीं, जो इसके लिए प्रासंगिक हैं (जैसे कि Oracle / OpenJDK जैसे कार्यान्वयन, लैम्ब्डा / मेथड हैंडल इंस्टेंसेस बनाने के लिए आवश्यक रैपर क्लास ऑब्जेक्ट बनाने के लिए ASM का उपयोग कैसे करते हैं।
अजाक्स

29

यह सब मेटाफैक्टरी के बारे में है

सबसे पहले, अधिकांश विधि संदर्भों को लैम्ब्डा मेटाफैक्टिक द्वारा अवरोहण की आवश्यकता नहीं होती है, उन्हें बस संदर्भ विधि के रूप में उपयोग किया जाता है। लैम्बडा एक्सप्रेशंस ("TLE") लेख के अनुवाद के अनुभाग "लैम्ब्डा बॉडी शुगरिंग" के तहत :

सभी चीजें समान हो रही हैं, गैर-निजी तरीकों के लिए निजी तरीके बेहतर हैं, उदाहरण के तरीकों के लिए स्थिर तरीके, यह सबसे अच्छा है अगर लैम्ब्डा निकायों को अंतरतम वर्ग में उतारा जाता है जिसमें लैम्ब्डा अभिव्यक्ति दिखाई देती है, हस्ताक्षर लैम्ब्डा के शरीर के हस्ताक्षर से मेल खाना चाहिए, अतिरिक्त कैप्चर किए गए मानों के लिए तर्क सूची के मोर्चे पर तर्कों को पूर्व निर्धारित किया जाना चाहिए, और विधि संदर्भों को बिल्कुल भी नहीं छोड़ना चाहिए। हालांकि, ऐसे अपवाद मामले हैं जहां हमें इस आधारभूत रणनीति से विचलित होना पड़ सकता है।

यह टीएलई के "द लैंबडा मेटाफैक्टिव" में और नीचे डाला गया है:

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

यह implतर्क लैम्ब्डा विधि की पहचान करता है, या तो एक लैम्बडा बॉडी या विधि संदर्भ में नामित विधि।

एक स्थिर ( Integer::sum) या अनबाउंड उदाहरण विधि ( Integer::intValue) संदर्भ 'सबसे सरल' या सबसे 'सुविधाजनक' हैं, इस अर्थ में कि वे बिना वंशज के 'फास्ट-पाथ' मेटाफैक्टिव वेरिएंट द्वारा बेहतर तरीके से संभाले जा सकते हैं । यह लाभ TLE के "मेटाफ़ेंक्टेबल वेरिएंट" में मददगार है।

उन तर्कों को समाप्त करने से, जहाँ उनकी ज़रूरत नहीं है, क्लासफ़ाइल्स छोटे हो जाते हैं। और फास्ट पथ विकल्प वीएम को लैम्ब्डा रूपांतरण ऑपरेशन को तीव्र करने के लिए बार को कम करता है, जिससे इसे "बॉक्सिंग" ऑपरेशन के रूप में माना जा सकता है और अनबॉक्स ऑप्टिमाइज़ेशन को सुगम बनाता है।

स्वाभाविक रूप से, एक आवृत्ति-कैप्चरिंग विधि संदर्भ ( obj::myMethod) को मंगलाचरण के तर्क के रूप में बंधे हुए उदाहरण को प्रदान करने की आवश्यकता होती है, जिसका अर्थ हो सकता है कि 'ब्रिज' विधियों का उपयोग करके डीग्रेगिंग की आवश्यकता हो।

निष्कर्ष

मुझे बिल्कुल यकीन नहीं है कि आप जिस लैम्ब्डा 'रैपर' पर इशारा कर रहे हैं, लेकिन भले ही आपके उपयोगकर्ता द्वारा परिभाषित लैम्ब्डा या विधि संदर्भों का उपयोग करने का अंतिम परिणाम एक ही हो, लेकिन जिस तरह से पहुंचा जाता है वह काफी अलग लगता है, और भविष्य में अगर यह अभी नहीं है तो अलग हो सकता है। इसलिए, मुझे लगता है कि यह अधिक से अधिक होने की संभावना नहीं है कि विधि संदर्भों को मेटाफ़ाइल द्वारा अधिक इष्टतम तरीके से नियंत्रित किया जा सकता है।


1
यह सबसे अधिक प्रासंगिक उत्तर है, क्योंकि यह वास्तव में लंबोदर बनाने के लिए आवश्यक आंतरिक मशीनरी पर छूता है। जैसा कि किसी ने वास्तव में लैम्ब्डा निर्माण प्रक्रिया को डिबग किया था (यह जानने के लिए कि किसी दिए गए लैम्ब्डा / विधि संदर्भ के लिए स्थिर आईडी कैसे उत्पन्न करें ताकि वे नक्शे से हटाए जा सकें), मुझे यह बहुत आश्चर्यजनक लगा कि बस कितना काम करना है एक मेमने का उदाहरण। Oracle / OpenJDK, गतिशील रूप से कक्षाएं उत्पन्न करने के लिए ASM का उपयोग करते हैं, जो यदि आवश्यक हो, तो आपके लैम्ब्डा में संदर्भित किसी भी चर को बंद करें (या विधि संदर्भ का उदाहरण क्वालीफायर) ...
Ajax

1
... चर पर बंद करने के अलावा, लैम्ब्डा विशेष रूप से एक स्थिर विधि भी बनाते हैं जिसे घोषित वर्ग में इंजेक्ट किया जाता है इसलिए बनाए गए लैम्ब्डा में फ़ंक्शन (मैप्स) में मैप करने के लिए आपको कॉल करना होगा। एक उदाहरण क्वालीफायर के साथ एक विधि संदर्भ इस विधि को इस विधि के लिए एक पैरामीटर के रूप में ले जाएगा; मैंने परीक्षण नहीं किया है अगर यह :: निजी वर्ग में यह विधि संदर्भ इस बंद को छोड़ देता है, लेकिन मैंने परीक्षण किया है कि स्थिर तरीके क्या करते हैं, वास्तव में, मध्यवर्ती विधि को छोड़ दें (और बस एक वस्तु बनाएं जो मंगलाचरण लक्ष्य के अनुरूप हो कॉल साइट)।
अजाक्स

1
संभवत: एक निजी पद्धति को भी वर्चुअल डिस्पैच की आवश्यकता नहीं होगी, जबकि कुछ भी अधिक जनता को उदाहरण (एक उत्पन्न स्थैतिक विधि के माध्यम से) को बंद करने की आवश्यकता होगी ताकि यह सुनिश्चित करने के लिए वास्तविक उदाहरण पर इनवोकवार्तिक हो सके कि यह नोटिस ओवरराइड हो जाए। टीएल; डीआर: विधि संदर्भ कम तर्कों के करीब है, इसलिए वे कम रिसाव करते हैं, और कम गतिशील कोड पीढ़ी की आवश्यकता होती है।
अजाक्स

3
अंतिम स्पैम टिप्पणी ... गतिशील वर्ग पीढ़ी बहुत भारी है। क्लास लोडर में अनाम वर्ग को लोड करने से बेहतर है (चूंकि सबसे भारी भागों को केवल एक बार किया जाता है), लेकिन आप वास्तव में आश्चर्यचकित होंगे कि लैंबडा उदाहरण बनाने में कितना काम होता है। यह दिलचस्प होगा कि लंबोदा बनाम विधि संदर्भ बनाम अनाम कक्षाओं के वास्तविक मानक देखें। ध्यान दें कि दो लैम्ब्डा या विधि संदर्भ जो समान चीजों को संदर्भित करते हैं, लेकिन अलग-अलग स्थानों पर रनटाइम के दौरान (यहां तक ​​कि एक ही विधि के भीतर, एक दूसरे के ठीक बाद) अलग-अलग कक्षाएं बनाते हैं।
अजाक्स

@ अजाक्स इस बारे में क्या? blog.soat.fr/2015/12/benchmark-java-lambda-vs-classe-anonyme
वालफ्रैट

0

लैम्ब्डा अभिव्यक्ति का उपयोग करते समय एक काफी गंभीर परिणाम होता है जो प्रदर्शन को प्रभावित कर सकता है।

जब आप एक लंबोदर अभिव्यक्ति की घोषणा करते हैं, तो आप स्थानीय दायरे पर बंद कर रहे हैं ।

इसका क्या मतलब है और यह प्रदर्शन को कैसे प्रभावित करता है?

वैसे मुझे खुशी है कि आपने पूछा। इसका मतलब यह है कि उन छोटे लंबो अभिव्यक्तियों में से प्रत्येक एक छोटा अनाम आंतरिक वर्ग है और इसका मतलब है कि यह इसके साथ सभी चर का संदर्भ देता है जो लंबोदर अभिव्यक्ति के समान दायरे में हैं।

इसका अर्थ thisवस्तु उदाहरण और उसके सभी क्षेत्रों का संदर्भ भी है । लैम्ब्डा के वास्तविक आह्वान के समय के आधार पर, यह एक महत्वपूर्ण संसाधन रिसाव की मात्रा हो सकती है क्योंकि कचरा संग्रहकर्ता इन संदर्भों को तब तक जाने में असमर्थ है जब तक कि लैम्बडा की वस्तु अभी भी जीवित है ...


समान गैर-स्थैतिक विधि संदर्भों के लिए जाता है।
अजाक्स

12
जावा लैम्ब्डा सख्ती से बंद नहीं होते हैं। न ही वे अनाम आंतरिक वर्ग हैं। वे उन सभी चरों का संदर्भ नहीं लेते हैं, जहां वे घोषित किए जाते हैं। वे केवल उन चरों का संदर्भ लेते हैं जिन्हें वे वास्तव में संदर्भित करते हैं। यह उस thisवस्तु पर भी लागू होता है जिसे संदर्भित किया जा सकता है। एक लंबोदर, इसलिए, केवल उनके लिए गुंजाइश होने से संसाधनों को लीक नहीं करता है; यह केवल उन वस्तुओं को धारण करता है जिनकी उसे आवश्यकता है। दूसरी ओर, एक अनाम आंतरिक वर्ग संसाधनों को लीक कर सकता है, लेकिन हमेशा ऐसा नहीं करता है। इस कोड को एक उदाहरण के लिए देखें: a.blmq.us/2mmrL6v
squid314

यह उत्तर केवल गलत है
स्टीफन रीच

धन्यवाद @StefanReich, यह बहुत मददगार था!
रोलैंड टीप
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.