JavaRush /وبلاگ جاوا /Random-FA /محبوب در مورد عبارات لامبدا در جاوا. همراه با مثال و کار....
Стас Пасинков
مرحله
Киев

محبوب در مورد عبارات لامبدا در جاوا. همراه با مثال و کار. قسمت 2

در گروه منتشر شد
این مقاله برای چه کسانی است؟
  • برای کسانی که قسمت اول این مقاله را می خوانند؛

  • برای کسانی که فکر می کنند قبلاً Java Core را به خوبی می شناسند، اما هیچ ایده ای در مورد عبارات لامبدا در جاوا ندارند. یا شاید قبلاً چیزی در مورد لامبدا شنیده باشید، اما بدون جزئیات.

  • برای کسانی که درک درستی از عبارات لامبدا دارند، اما هنوز از استفاده از آنها می ترسند و غیرعادی هستند.

دسترسی به متغیرهای خارجی

آیا این کد با یک کلاس ناشناس کامپایل می شود؟
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(). مقدار خود متغیر در حین اجرای برنامه تغییر نمی کند و همیشه به همان شی اشاره می کند که به ما امکان می دهد یک متغیر را بلافاصله با کلمه کلیدی اعلان کنیم final. نمونه های مشابه، اما با عبارات لامبدا:
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("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
اگرچه این متد staticMethod()خصوصی است، اما برای فراخوانی در داخل متد «دسترس» است main()، بنابراین فراخوانی از داخل لامبدا ایجاد شده در متد نیز قابل دسترسی است main.

لحظه اجرای کد بیان لامبدا

این سوال ممکن است برای شما خیلی ساده به نظر برسد، اما همه آن ارزش پرسیدن را دارد: کد داخل عبارت لامبدا چه زمانی اجرا می شود؟ در لحظه خلقت؟ یا در لحظه ای که (هنوز معلوم نیست کجا) فراخوانی می شود؟ بررسی آن بسیار آسان است.
System.out.println("Запуск программы");

// много всякого разного codeа
// ...

System.out.println("Перед объявлением лямбды");

Runnable runnable = () -> System.out.println("Я - лямбда!");

System.out.println("После объявления лямбды");

// много всякого другого codeа
// ...

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
خروجی روی نمایشگر:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
مشاهده می شود که کد عبارت لامبدا در انتها، پس از ایجاد thread و تنها زمانی که فرآیند اجرای برنامه به اجرای واقعی متد رسید، اجرا شد run(). و اصلا در زمان اعلام آن. با اعلان یک عبارت لامبدا، ما فقط یک شی از نوع ایجاد کردیم Runnableو رفتار روش آن را شرح دادیم run(). خود این روش خیلی دیرتر راه اندازی شد.

مراجع روش؟

مستقیماً به خود لامبدا مربوط نمی شود، اما فکر می کنم منطقی باشد که در این مقاله چند کلمه در مورد آن بگویم. فرض کنید یک عبارت لامبدا داریم که کار خاصی انجام نمی دهد، اما فقط یک متد را فراخوانی می کند.
x -> System.out.println(x)
چیزی به او دادند хو به سادگی او را صدا زد System.out.println()و از آنجا عبور داد х. در این صورت می توانیم آن را با پیوندی به روش مورد نیاز خود جایگزین کنیم. مثل این:
System.out::println
بله، بدون پرانتز در پایان! مثال کامل تر:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
در خط آخر از روشی استفاده می کنیم forEach()که یک شی رابط را می پذیرد Consumer. این دوباره یک رابط کاربردی با تنها یک روش است void accept(T t). بر این اساس، یک عبارت لامبدا می نویسیم که یک پارامتر را می گیرد (از آنجایی که در خود اینترفیس تایپ می شود، نوع پارامتر را نشان نمی دهیم، بلکه نشان می دهیم که فراخوانی خواهد شد х). در بدنه عبارت لامبدا، کد را می نویسیم. که با فراخوانی متد اجرا می شود accept(). در اینجا ما به سادگی آنچه در متغیر موجود است را روی صفحه نمایش می دهیم х. خود متد forEach()از تمام عناصر مجموعه عبور می کند، Consumerمتد شیء رابط ارسال شده به آن (لامبدای ما) را فراخوانی می کند accept(). جایی که هر عنصر از مجموعه را منتقل می کند. همانطور که قبلاً گفتم، این یک بیان لامبدا است (به سادگی روش دیگری را فراخوانی می کند) که می توانیم آن را با ارجاع به متدی که نیاز داریم جایگزین کنیم. سپس کد ما به شکل زیر خواهد بود:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
نکته اصلی این است که پارامترهای پذیرفته شده از روش ها (println()و accept()). از آنجایی که متد println()می تواند هر چیزی را بپذیرد (برای همه چیزهای اولیه و برای هر شی بیش از حد بارگذاری شده است)، به جای عبارت لامبدا، می توانیم forEach()فقط یک مرجع به متد ارسال کنیم println(). سپس forEach()هر عنصر مجموعه را می گیرد و مستقیماً به آن ارسال می کند. متد println()برای کسانی که برای اولین بار با این مورد مواجه می شوند، لطفاً توجه داشته باشید لطفاً توجه داشته باشید که ما متد را System.out.println()(با نقطه بین کلمات و با پرانتز در انتها) فراخوانی نمی کنیم، بلکه مرجع را به خود این روش منتقل می کنیم.
strings.forEach(System.out.println());
یک خطای کامپایل خواهیم داشت. چون قبل از فراخوانی forEach()، جاوا می بیند که در حال فراخوانی است System.out.println()، متوجه می شود که در حال برگرداندن است voidو سعی می کند این را به شی از نوع که در آنجا منتظر است voidمنتقل کند . forEach()Consumer

نحو برای استفاده از مراجع روش

خیلی ساده است:
  1. ارسال ارجاع به روش ایستاNameКласса:: NameСтатическогоМетода?

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            strings.forEach(Main::staticMethod);
        }
    
        private static void staticMethod(String s) {
            // do something
        }
    }
  2. ارسال ارجاع به یک روش غیر ایستا با استفاده از یک شی موجودNameПеременнойСОбъектом:: method name

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            Main instance = new Main();
            strings.forEach(instance::nonStaticMethod);
        }
    
        private void nonStaticMethod(String s) {
            // do something
        }
    }
  3. با استفاده از کلاسی که چنین متدی در آن پیاده سازی شده است، یک مرجع به یک متد غیراستاتیک ارسال می کنیمNameКласса:: method name

    public class Main {
        public static void main(String[] args) {
            List<User> users = new LinkedList<>();
            users.add(new User("Vasya"));
            users.add(new User("Коля"));
            users.add(new User("Петя"));
    
            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. ارسال پیوند به سازنده NameКласса::new
    استفاده از پیوندهای متد زمانی بسیار راحت است که یک روش آماده وجود داشته باشد که شما کاملا از آن راضی هستید و دوست دارید از آن به عنوان یک کال بک استفاده کنید. در این حالت، به جای نوشتن یک عبارت لامبدا با کد آن متد، یا یک عبارت لامبدا که به سادگی این متد را فراخوانی می کنیم، به سادگی یک مرجع به آن ارسال می کنیم. همین.

تفاوت جالب بین کلاس ناشناس و عبارت لامبدا

در یک کلاس ناشناس، کلمه کلیدی thisبه یک شی از آن کلاس ناشناس اشاره می کند. و اگر از آن در داخل لامبدا استفاده کنیم this، به شیء کلاس فریمینگ دسترسی خواهیم داشت. جایی که ما واقعاً این عبارت را نوشتیم. این اتفاق می‌افتد زیرا عبارات لامبدا، زمانی که کامپایل می‌شوند، به یک متد خصوصی کلاسی که در آن نوشته می‌شوند تبدیل می‌شوند. من استفاده از این "ویژگی" را توصیه نمی کنم، زیرا دارای یک عارضه جانبی است که با اصول برنامه نویسی عملکردی در تضاد است. اما این رویکرد کاملاً با OOP سازگار است. ;)

من اطلاعات را از کجا آوردم یا چه چیز دیگری را بخوانم

نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION