CodeGym /Java Blog /अनियमित /जावा में लैम्ब्डा एक्सप्रेशन की व्याख्या। उदाहरणों और कार...
John Squirrels
स्तर 41
San Francisco

जावा में लैम्ब्डा एक्सप्रेशन की व्याख्या। उदाहरणों और कार्यों के साथ। भाग 2

अनियमित ग्रुप में प्रकाशित
यह लेख किसके लिए है?
  • यह उन लोगों के लिए है जो इस लेख का पहला भाग पढ़ते हैं;
  • यह उन लोगों के लिए है जो सोचते हैं कि वे पहले से ही जावा कोर को अच्छी तरह से जानते हैं, लेकिन जावा में लैम्ब्डा एक्सप्रेशन के बारे में कोई जानकारी नहीं है। या हो सकता है कि उन्होंने लैम्ब्डा भावों के बारे में कुछ सुना हो, लेकिन विवरण की कमी है।
  • यह उन लोगों के लिए है जिन्हें लैम्ब्डा अभिव्यक्तियों की एक निश्चित समझ है, लेकिन वे अभी भी उनके द्वारा भयभीत हैं और उनका उपयोग करने के लिए अभ्यस्त नहीं हैं।
यदि आप इनमें से किसी एक श्रेणी में फिट नहीं बैठते हैं, तो आपको यह लेख उबाऊ, त्रुटिपूर्ण, या आम तौर पर आपकी चाय का प्याला नहीं लग सकता है। इस मामले में, अन्य चीजों पर जाने के लिए स्वतंत्र महसूस करें या, यदि आप इस विषय में अच्छी तरह से वाकिफ हैं, तो कृपया टिप्पणियों में सुझाव दें कि मैं लेख को कैसे सुधार या पूरक कर सकता हूं। जावा में लैम्ब्डा एक्सप्रेशन की व्याख्या।  उदाहरणों और कार्यों के साथ।  भाग 2 - 1सामग्री किसी भी अकादमिक मूल्य का दावा नहीं करती है, अकेले नवीनता दें। इसके विपरीत: मैं उन चीजों का वर्णन करने की कोशिश करूंगा जो जटिल हैं (कुछ लोगों के लिए) यथासंभव सरलता से। स्ट्रीम एपीआई की व्याख्या करने के अनुरोध ने मुझे इसे लिखने के लिए प्रेरित किया। मैंने इसके बारे में सोचा और फैसला किया कि लैम्ब्डा एक्सप्रेशन को समझे बिना मेरे कुछ स्ट्रीम उदाहरण समझ से बाहर होंगे। तो हम लैम्ब्डा एक्सप्रेशंस के साथ शुरुआत करेंगे।

बाहरी चरों तक पहुंच

क्या यह कोड अज्ञात वर्ग से संकलित है?

int counter = 0;
Runnable r = new Runnable() { 

    @Override 
    public void run() { 
        counter++;
    }
};
नहीं। counter चर होना चाहिए final। या यदि नहीं final, तो कम से कम यह अपना मूल्य नहीं बदल सकता। लैम्ब्डा एक्सप्रेशन में भी यही सिद्धांत लागू होता है। वे उन सभी चरों तक पहुंच सकते हैं जिन्हें वे घोषित किए गए स्थान से "देख" सकते हैं। लेकिन एक लैम्ब्डा उन्हें नहीं बदलना चाहिए (उन्हें एक नया मान दें)। हालाँकि, अनाम कक्षाओं में इस प्रतिबंध को बायपास करने का एक तरीका है। बस एक रेफरेंस वेरिएबल बनाएं और ऑब्जेक्ट की आंतरिक स्थिति को बदलें। ऐसा करने में, चर स्वयं नहीं बदलता है (उसी वस्तु की ओर इशारा करता है) और इसे सुरक्षित रूप से चिह्नित किया जा सकता है final

final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() { 

    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
यहाँ हमारा चर एक वस्तु counterका संदर्भ है । AtomicIntegerऔर incrementAndGet()इस वस्तु की स्थिति को बदलने के लिए विधि का उपयोग किया जाता है। प्रोग्राम के चलने के दौरान वेरिएबल का मान नहीं बदलता है। यह हमेशा एक ही वस्तु की ओर इशारा करता है, जो हमें अंतिम कीवर्ड के साथ चर घोषित करने देता है। यहाँ एक ही उदाहरण हैं, लेकिन लैम्ब्डा एक्सप्रेशन के साथ:

int counter = 0;
Runnable r = () -> counter++;
यह अज्ञात वर्ग वाले संस्करण के समान कारण से संकलित नहीं होगा:  counterप्रोग्राम चलने के दौरान बदलना नहीं चाहिए। लेकिन सब कुछ ठीक है अगर हम इसे इस तरह करें:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
यह कॉलिंग विधियों पर भी लागू होता है। लैम्ब्डा अभिव्यक्तियों के भीतर, आप न केवल सभी "दृश्यमान" चरों तक पहुंच सकते हैं, बल्कि किसी भी सुलभ विधियों को भी कॉल कर सकते हैं।

public class Main { 

    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    } 

    private static void staticMethod() { 

        System.out.println("I'm staticMethod(), and someone just called me!");
    }
}
हालांकि staticMethod()यह निजी है, यह विधि के अंदर पहुंच योग्य है , इसलिए इसे विधि main()में बनाए गए लैम्ब्डा के अंदर से भी बुलाया जा सकता है।main

लैम्ब्डा अभिव्यक्ति कब निष्पादित की जाती है?

आपको निम्न प्रश्न बहुत सरल लग सकता है, लेकिन आपको इसे वही पूछना चाहिए: लैम्ब्डा अभिव्यक्ति के अंदर कोड कब निष्पादित किया जाएगा? यह कब बनाया जाता है? या कब कहा जाता है (जो अभी ज्ञात नहीं है)? यह जांचना काफी आसान है।

System.out.println("Program start"); 

// All sorts of code here
// ...

System.out.println("Before lambda declaration");

Runnable runnable = () -> System.out.println("I'm a lambda!");

System.out.println("After lambda declaration"); 

// All sorts of other code here
// ...

System.out.println("Before passing the lambda to the thread");
new Thread(runnable).start(); 
स्क्रीन आउटपुट:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
आप देख सकते हैं कि लैम्ब्डा एक्सप्रेशन को बहुत अंत में निष्पादित किया गया था, थ्रेड बनने के बाद और केवल तभी जब प्रोग्राम का निष्पादन run()विधि तक पहुँचता है। निश्चित रूप से तब नहीं जब यह घोषित किया जाता है। लैम्ब्डा एक्सप्रेशन की घोषणा करके, हमने केवल एक Runnableवस्तु बनाई है और बताया है कि इसकी run()विधि कैसे व्यवहार करती है। विधि को बहुत बाद में निष्पादित किया जाता है।

विधि संदर्भ?

विधि संदर्भ सीधे लैम्ब्डा से संबंधित नहीं हैं, लेकिन मुझे लगता है कि इस लेख में उनके बारे में कुछ शब्द कहना समझ में आता है। मान लीजिए कि हमारे पास एक लैम्ब्डा एक्सप्रेशन है जो कुछ खास नहीं करता है, लेकिन बस एक विधि कहता है।

x -> System.out.println(x)
यह कुछ xऔर बस कॉल प्राप्त करता है System.out.println(), पास हो रहा है x। इस मामले में, हम इसे वांछित विधि के संदर्भ में बदल सकते हैं। इस कदर:

System.out::println
यह सही है - अंत में कोई कोष्ठक नहीं! यहाँ एक और पूर्ण उदाहरण दिया गया है:

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo"); 

strings.forEach(x -> System.out.println(x));
अंतिम पंक्ति में, हम उस forEach()विधि का उपयोग करते हैं, जो एक ऐसी वस्तु लेती है जो Consumerइंटरफ़ेस को लागू करती है। दोबारा, यह एक कार्यात्मक इंटरफ़ेस है, जिसमें केवल एक void accept(T t)विधि है। तदनुसार, हम एक लैम्ब्डा अभिव्यक्ति लिखते हैं जिसमें एक पैरामीटर होता है (क्योंकि यह इंटरफ़ेस में ही टाइप किया जाता है, हम पैरामीटर प्रकार निर्दिष्ट नहीं करते हैं; हम केवल संकेत देते हैं कि हम इसे कॉल करेंगे) x। लैम्ब्डा एक्सप्रेशन के शरीर में, हम उस कोड को लिखते हैं जिसे accept()विधि कहे जाने पर निष्पादित किया जाएगा। xयहां हम केवल यह प्रदर्शित करते हैं कि वेरिएबल में क्या समाप्त हुआ । यह वही forEach()विधि संग्रह में सभी तत्वों के माध्यम से पुनरावृत्त करती है और accept()कार्यान्वयन पर विधि को कॉल करती हैConsumerइंटरफ़ेस (हमारा लैम्ब्डा), संग्रह में प्रत्येक आइटम में गुजर रहा है। जैसा कि मैंने कहा, हम वांछित विधि के संदर्भ में ऐसी लैम्ब्डा अभिव्यक्ति (वह जो केवल एक अलग विधि को वर्गीकृत करते हैं) को प्रतिस्थापित कर सकते हैं। तब हमारा कोड इस तरह दिखेगा:

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo");

strings.forEach(System.out::println);
println()मुख्य बात यह है कि और accept()विधियों के पैरामीटर मेल खाते हैं। चूंकि println()विधि कुछ भी स्वीकार कर सकती है (यह सभी प्राइमेटिव प्रकारों और सभी वस्तुओं के लिए अधिभारित है), लैम्ब्डा अभिव्यक्तियों के बजाय, हम बस विधि के संदर्भ को पास कर सकते println()हैं forEach()। फिर forEach()प्रत्येक तत्व को संग्रह में ले जाएगा और इसे सीधे println()विधि में पास कर देगा। पहली बार इसका सामना करने वाले किसी के लिए, कृपया ध्यान दें कि हम कॉल नहीं कर रहे हैं System.out.println()(शब्दों के बीच डॉट्स और अंत में कोष्ठक के साथ)। इसके बजाय, हम इस पद्धति का संदर्भ दे रहे हैं। अगर हम इसे लिखते हैं

strings.forEach(System.out.println());
हमारे पास एक संकलन त्रुटि होगी। कॉल करने से पहले forEach(), जावा देखता है कि System.out.println()कॉल किया जा रहा है, इसलिए यह समझता है कि वापसी मान है और पास करने voidका प्रयास करेगा , जो ऑब्जेक्ट की अपेक्षा कर रहा है। voidforEach()Consumer

विधि संदर्भों के लिए सिंटेक्स

यह काफी आसान है:
  1. हम इस तरह एक स्थिर विधि का संदर्भ देते हैं:ClassName::staticMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>(); 
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            strings.forEach(Main::staticMethod); 
        } 
    
        private static void staticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  2. हम किसी मौजूदा वस्तु का उपयोग करके एक गैर-स्थैतिक विधि का संदर्भ देते हैं, जैसे:objectName::instanceMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>();
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            Main instance = new Main(); 
            strings.forEach(instance::nonStaticMethod); 
        } 
    
        private void nonStaticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  3. हम उस वर्ग का उपयोग करके एक गैर-स्थैतिक विधि का संदर्भ देते हैं जो इसे निम्नानुसार लागू करता है:ClassName::methodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<User> users = new LinkedList<>(); 
            users.add (new User("John")); 
            users.add(new User("Paul")); 
            users.add(new User("George")); 
    
            users.forEach(User::print); 
        } 
    
        private static class User { 
            private String name; 
    
            private User(String name) { 
                this.name = name; 
            } 
    
            private void print() { 
                System.out.println(name); 
            } 
        } 
    }
    
  4. हम इस तरह से एक कंस्ट्रक्टर का संदर्भ देते हैं:ClassName::new

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

अज्ञात वर्गों और लैम्ब्डा अभिव्यक्तियों के बीच एक दिलचस्प अंतर

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

मुझे अपनी जानकारी कहां से मिली और आपको और क्या पढ़ना चाहिए?

और, ज़ाहिर है, मुझे Google पर ढेर सारी चीज़ें मिलीं :)
टिप्पणियां
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION