CodeGym /Java Blog /यादृच्छिक /जावा मधील लॅम्बडा अभिव्यक्तींचे स्पष्टीकरण. उदाहरणे आणि क...
John Squirrels
पातळी 41
San Francisco

जावा मधील लॅम्बडा अभिव्यक्तींचे स्पष्टीकरण. उदाहरणे आणि कार्यांसह. भाग 2

यादृच्छिक या ग्रुपमध्ये प्रकाशित केले
हा लेख कोणासाठी आहे?
  • या लेखाचा पहिला भाग वाचलेल्या लोकांसाठी आहे ;
  • हे अशा लोकांसाठी आहे ज्यांना वाटते की त्यांना जावा कोअर आधीच चांगले माहित आहे, परंतु त्यांना जावामधील लॅम्बडा अभिव्यक्तीबद्दल काहीच माहिती नाही. किंवा कदाचित त्यांनी लॅम्बडा अभिव्यक्तींबद्दल काहीतरी ऐकले असेल, परंतु तपशीलांची कमतरता आहे.
  • हे अशा लोकांसाठी आहे ज्यांना लॅम्बडा अभिव्यक्तींची विशिष्ट समज आहे, परंतु तरीही ते घाबरलेले आहेत आणि ते वापरण्याची सवय नाही.
जर तुम्ही यापैकी एका वर्गवारीत बसत नसाल, तर तुम्हाला हा लेख कंटाळवाणा, सदोष किंवा सामान्यतः तुमचा चहाचा कप वाटू शकत नाही. या प्रकरणात, इतर गोष्टींकडे जाण्यास मोकळ्या मनाने किंवा, जर तुम्हाला या विषयात पारंगत असेल, तर कृपया मी लेखात सुधारणा किंवा पूरक कसे बनवू शकेन याबद्दल टिप्पण्यांमध्ये सूचना द्या. जावा मधील लॅम्बडा अभिव्यक्तींचे स्पष्टीकरण.  उदाहरणे आणि कार्यांसह.  भाग २ - १साहित्यात कोणतेही शैक्षणिक मूल्य असल्याचा दावा करत नाही, नवीनता सोडा. अगदी उलट: मी शक्य तितक्या क्लिष्ट (काही लोकांसाठी) गोष्टींचे वर्णन करण्याचा प्रयत्न करेन. स्ट्रीम API स्पष्ट करण्याच्या विनंतीने मला हे लिहिण्यास प्रेरित केले. मी त्याबद्दल विचार केला आणि ठरवले की माझ्या प्रवाहातील काही उदाहरणे लॅम्बडा अभिव्यक्ती समजून घेतल्याशिवाय अनाकलनीय असतील. तर आपण lambda expressions सह सुरुवात करू.

बाह्य चलांमध्ये प्रवेश

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

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.

लॅम्बडा अभिव्यक्ती कधी अंमलात आणली जाते?

तुम्हाला खालील प्रश्न खूप सोपा वाटू शकतो, परंतु तुम्ही तो तसाच विचारला पाहिजे: lambda एक्स्प्रेशनमधील कोड कधी अंमलात येईल? ते कधी निर्माण होते? किंवा जेव्हा ते म्हणतात (जे अद्याप माहित नाही)? हे तपासणे बऱ्यापैकी सोपे आहे.

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!
तुम्ही पाहू शकता की lambda एक्स्प्रेशन अगदी शेवटी अंमलात आणले गेले होते, थ्रेड तयार केल्यानंतर आणि जेव्हा प्रोग्रामची अंमलबजावणी पद्धतपर्यंत पोहोचते run(). ते घोषित केल्यावर नक्कीच नाही. लॅम्बडा अभिव्यक्ती घोषित करून, आम्ही फक्त एक Runnableऑब्जेक्ट तयार केला आहे आणि त्याची run()पद्धत कशी वागते याचे वर्णन केले आहे. पद्धत स्वतःच खूप नंतर अंमलात आणली जाते.

पद्धतीचे संदर्भ?

पद्धती संदर्भ थेट lambdas संबंधित नाहीत, पण मला वाटते की या लेखात त्यांच्याबद्दल काही शब्द बोलणे अर्थपूर्ण आहे. समजा आपल्याकडे लॅम्बडा अभिव्यक्ती आहे जी काही विशेष करत नाही, परंतु फक्त एक पद्धत कॉल करते.

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(), Java पाहतो की 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कीवर्ड अनामित वर्गाच्या ऑब्जेक्टकडे निर्देश करतो. परंतु जर आपण हे लॅम्बडाच्या आत वापरला, तर आपल्याला समाविष्ट असलेल्या वर्गाच्या ऑब्जेक्टमध्ये प्रवेश मिळेल. एक जेथे आम्ही प्रत्यक्षात lambda अभिव्यक्ती लिहिले. असे घडते कारण लॅम्बडा अभिव्यक्ती ते ज्या वर्गात लिहिले आहेत त्या खाजगी पद्धतीमध्ये संकलित केले जातात. मी हे "वैशिष्ट्य" वापरण्याची शिफारस करणार नाही, कारण त्याचे दुष्परिणाम आहेत आणि ते कार्यात्मक प्रोग्रामिंगच्या तत्त्वांना विरोध करतात. ते म्हणाले, हा दृष्टिकोन OOP शी पूर्णपणे सुसंगत आहे. ;)

मला माझी माहिती कुठे मिळाली आणि तुम्ही आणखी काय वाचले पाहिजे?

आणि, अर्थातच, मला Google वर भरपूर सामग्री सापडली :)
टिप्पण्या
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION