CodeGym /Java Blog /এলোমেলো /জাভাতে ল্যাম্বডা এক্সপ্রেশনের একটি ব্যাখ্যা। উদাহরণ এবং ক...
John Squirrels
লেভেল 41
San Francisco

জাভাতে ল্যাম্বডা এক্সপ্রেশনের একটি ব্যাখ্যা। উদাহরণ এবং কর্ম সহ. অংশ ২

এলোমেলো দলে প্রকাশিত
এই নিবন্ধটি কার জন্য?
  • যারা এই নিবন্ধের প্রথম অংশ পড়েছেন তাদের জন্য এটি ;
  • এটি এমন লোকদের জন্য যারা মনে করেন তারা ইতিমধ্যেই জাভা কোর ভাল জানেন, কিন্তু জাভাতে ল্যাম্বডা এক্সপ্রেশন সম্পর্কে তাদের কোন ধারণা নেই। অথবা হয়তো তারা ল্যাম্বডা এক্সপ্রেশন সম্পর্কে কিছু শুনেছে, কিন্তু বিশদ বিবরণের অভাব রয়েছে।
  • এটি এমন লোকেদের জন্য যাদের ল্যাম্বডা অভিব্যক্তি সম্পর্কে একটি নির্দিষ্ট ধারণা রয়েছে, কিন্তু এখনও তাদের দ্বারা হতাশ এবং সেগুলি ব্যবহার করতে অভ্যস্ত নয়।
আপনি যদি এই বিভাগগুলির মধ্যে একটির সাথে মানানসই না হন, তাহলে আপনি এই নিবন্ধটি বিরক্তিকর, ত্রুটিপূর্ণ বা সাধারণত আপনার চায়ের কাপ না বলে মনে করতে পারেন। এই ক্ষেত্রে, নির্দ্বিধায় অন্য জিনিসগুলিতে এগিয়ে যান বা, আপনি যদি এই বিষয়ে ভালভাবে পারদর্শী হন তবে অনুগ্রহ করে মন্তব্যে পরামর্শ দিন যে আমি কীভাবে নিবন্ধটি উন্নত করতে পারি বা পরিপূরক করতে পারি। জাভাতে ল্যাম্বডা এক্সপ্রেশনের একটি ব্যাখ্যা।  উদাহরণ এবং কর্ম সহ.  পর্ব 2 - 1উপাদানটির কোনো একাডেমিক মান আছে বলে দাবি করে না, নতুনত্ব ছেড়ে দিন। একেবারে বিপরীত: আমি যতটা সম্ভব জটিল (কিছু লোকের জন্য) জিনিসগুলি বর্ণনা করার চেষ্টা করব। স্ট্রিম API ব্যাখ্যা করার জন্য একটি অনুরোধ আমাকে এটি লিখতে অনুপ্রাণিত করেছে। আমি এটি সম্পর্কে চিন্তা করেছি এবং সিদ্ধান্ত নিয়েছি যে আমার কিছু প্রবাহের উদাহরণ ল্যাম্বডা অভিব্যক্তির বোঝা ছাড়াই বোধগম্য হবে। তাই আমরা ল্যাম্বডা এক্সপ্রেশন দিয়ে শুরু করব।

বাহ্যিক ভেরিয়েবল অ্যাক্সেস

এই কোড একটি বেনামী ক্লাস সঙ্গে কম্পাইল?

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কীওয়ার্ডটি বেনামী ক্লাসের একটি বস্তুকে নির্দেশ করে। কিন্তু যদি আমরা এটি একটি ল্যাম্বডার ভিতরে ব্যবহার করি, তাহলে আমরা সম্বলিত শ্রেণীর বস্তুতে অ্যাক্সেস লাভ করি। যেখানে আমরা আসলে ল্যাম্বডা এক্সপ্রেশন লিখেছি। এটি ঘটে কারণ ল্যাম্বডা এক্সপ্রেশনগুলি ক্লাসের একটি ব্যক্তিগত পদ্ধতিতে সংকলিত হয় যেটিতে তারা লেখা হয়েছে৷ আমি এই "বৈশিষ্ট্য" ব্যবহার করার সুপারিশ করব না, কারণ এটির একটি পার্শ্ব প্রতিক্রিয়া রয়েছে এবং এটি কার্যকরী প্রোগ্রামিংয়ের নীতিগুলির সাথে বিরোধিতা করে৷ এটি বলেছে, এই পদ্ধতিটি সম্পূর্ণরূপে OOP এর সাথে সামঞ্জস্যপূর্ণ। ;)

আমি আমার তথ্য কোথায় পেলাম এবং আপনার আর কি পড়া উচিত?

এবং, অবশ্যই, আমি গুগলে প্রচুর জিনিস পেয়েছি :)
মন্তব্য
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION