JavaRush /Java blogi /Random-UZ /Java'da lambda ifodalari haqida mashhur. Misollar va tops...

Java'da lambda ifodalari haqida mashhur. Misollar va topshiriqlar bilan. 2-qism

Guruhda nashr etilgan
Bu maqola kim uchun?
  • Ushbu maqolaning birinchi qismini o'qiganlar uchun ;

  • Java Core-ni allaqachon yaxshi bilaman deb o'ylaydiganlar uchun, lekin Java-da lambda ifodalari haqida hech qanday tasavvurga ega emaslar. Yoki, ehtimol, siz allaqachon lambdalar haqida biror narsa eshitgansiz, ammo tafsilotlarsiz.

  • lambda iboralari haqida bir oz tushunchaga ega bo'lganlar uchun, lekin ularni ishlatishdan qo'rqadigan va g'ayrioddiy.

Tashqi o'zgaruvchilarga kirish

Ushbu kod anonim sinf bilan kompilyatsiya qilinadimi?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
Yo'q. O'zgaruvchi counterbo'lishi kerak final. Yoki shart emas final, lekin har qanday holatda ham uning qiymatini o'zgartira olmaydi. Xuddi shu printsip lambda ifodalarida qo'llaniladi. Ular e'lon qilingan joydan ularga "ko'rinadigan" barcha o'zgaruvchilarga kirish huquqiga ega. Lekin lambda ularni o'zgartirmasligi kerak (yangi qiymat tayinlash). To'g'ri, anonim sinflarda bu cheklovni chetlab o'tish imkoniyati mavjud. Faqat mos yozuvlar turidagi o'zgaruvchini yaratish va ob'ektning ichki holatini o'zgartirish kifoya. Bunday holda, o'zgaruvchining o'zi bir xil ob'ektga ishora qiladi va bu holda siz uni xavfsiz tarzda ko'rsatishingiz mumkin final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Bu erda bizning o'zgaruvchimiz counterturdagi ob'ektga havola AtomicInteger. Va bu ob'ektning holatini o'zgartirish uchun usul ishlatiladi incrementAndGet(). O'zgaruvchining qiymati dastur ishlayotgan vaqtda o'zgarmaydi va har doim bir xil ob'ektga ishora qiladi, bu esa o'zgaruvchini kalit so'zi bilan darhol e'lon qilish imkonini beradi final. Xuddi shu misollar, lekin lambda ifodalari bilan:
int counter = 0;
Runnable r = () -> counter++;
U anonim sinfga ega variant bilan bir xil sababga ko'ra kompilyatsiya qilmaydi: counterdastur ishlayotgan paytda u o'zgarmasligi kerak. Ammo shunga o'xshash - hamma narsa yaxshi:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Bu qo'ng'iroq qilish usullariga ham tegishli. Lambda ifodasi ichidan siz nafaqat barcha "ko'rinadigan" o'zgaruvchilarga kirishingiz, balki siz kirishingiz mumkin bo'lgan usullarni ham chaqirishingiz mumkin.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    }

    private static void staticMethod() {
        System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
Usul staticMethod()shaxsiy bo'lsa-da, uni usul ichida chaqirish mumkin main(), shuning uchun usulda yaratilgan lambda ichidan qo'ng'iroq qilish ham mumkin main.

Lambda ifoda kodini bajarish momenti

Bu savol sizga juda oddiydek tuyulishi mumkin, ammo shuni so'rashga arziydi: lambda ifodasi ichidagi kod qachon bajariladi? Yaratilish vaqtida? Yoki qachon (hali noma'lum) qachon chaqiriladi? Buni tekshirish juda oson.
System.out.println("Запуск программы");

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

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

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

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

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

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Displeydagi chiqish:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
Ko'rinib turibdiki, lambda ifoda kodi eng oxirida, ip yaratilgandan so'ng va faqat dasturni bajarish jarayoni usulning haqiqiy bajarilishiga etganida bajarilgan run(). Va u e'lon qilingan paytda umuman emas. Lambda ifodasini e'lon qilish orqali biz faqat turdagi ob'ektni yaratdik Runnableva uning usulining harakatini tasvirladik run(). Usulning o'zi ancha keyinroq ishga tushirilgan.

Usul havolalari?

Lambdalarning o'zlari bilan bevosita bog'liq emas, lekin menimcha, bu maqolada bu haqda bir necha so'z aytish mantiqan to'g'ri keladi. Aytaylik, bizda lambda ifodasi bor, u hech qanday maxsus ish qilmaydi, faqat qandaydir usulni chaqiradi.
x -> System.out.println(x)
Ular unga nimadir berishdi хva u shunchaki uni chaqirib System.out.println(), u erdan o'tib ketdi х. Bunday holda, biz uni kerakli usulga havola bilan almashtirishimiz mumkin. Mana bunday:
System.out::println
Ha, oxirida qavssiz! To'liqroq misol:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
Oxirgi satrda forEach()interfeys ob'ektini qabul qiladigan usuldan foydalanamiz Consumer. Bu yana faqat bir usul bilan funktsional interfeysi void accept(T t). Shunga ko'ra, biz bitta parametrni qabul qiladigan lambda ifodasini yozamiz (u interfeysning o'zida yozilganligi sababli, biz parametrning turini ko'rsatmaymiz, balki uning chaqirilishini ko'rsatamiz х). Lambda ifodasining tanasiga kodni yozamiz. Bu usul chaqirilganda bajariladi.Bu accept()yerda biz shunchaki ekranda oʻzgaruvchida nima borligini koʻrsatamiz.Usulning хoʻzi forEach()toʻplamning barcha elementlaridan oʻtadi, Consumerunga oʻtgan interfeys obʼyektining metodini chaqiradi (bizning lambda) accept(), Bu to'plamdagi har bir elementni o'tkazadigan joy. Yuqorida aytib o'tganimdek, bu lambda ifodasi (shunchaki boshqa usulni chaqirish) biz o'zimizga kerak bo'lgan usulga havola bilan almashtirishimiz mumkin.Keyin bizning kodimiz quyidagicha ko'rinadi:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
Asosiysi, usullarning qabul qilingan parametrlari (println()va accept()). Usul println()har qanday narsani qabul qilishi mumkinligi sababli (barcha ibtidoiylar va har qanday ob'ektlar uchun haddan tashqari yuklangan) lambda ifodasi o'rniga biz forEach()faqat usulga havola qilishimiz mumkin println(). Keyin forEach()u to'plamning har bir elementini oladi va uni to'g'ridan-to'g'ri uzatadi. Ushbu usulga println()birinchi marta duch kelganlar uchun, iltimos, diqqat qiling: biz usulni chaqirmaymiz System.out.println()(so'zlar orasidagi nuqta va oxirida qavslar bilan), balki biz ushbu usulning o'ziga havola qilamiz.
strings.forEach(System.out.println());
kompilyatsiya xatosiga duch kelamiz. Chunki qo'ng'iroqdan oldin forEach()Java chaqirilayotganini ko'radi System.out.println(), qaytarilayotganini tushunadi va uni u erda kutib turgan ob'ektga o'tkazishga voidharakat qiladi . voidforEach()Consumer

Usul havolalaridan foydalanish sintaksisi

Bu juda oddiy:
  1. Statik usulga havolani o'tkazishNameКласса:: 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. Mavjud ob'ekt yordamida statik bo'lmagan usulga havolani o'tkazishNameПеременнойСОбъектом:: 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. Biz statik bo'lmagan usulga havolani bunday usul amalga oshirilgan sinfdan foydalanib o'tkazamizNameКласса:: 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. Konstruktorga havolani o'tkazish NameКласса::new
    Usul havolalaridan foydalanish sizni to'liq qoniqtiradigan tayyor usul mavjud bo'lganda va uni qayta qo'ng'iroq sifatida ishlatmoqchi bo'lganingizda juda qulaydir. Bunday holda, ushbu usulning kodi bilan lambda ifodasini yoki biz ushbu usulni oddiygina chaqiradigan lambda ifodasini yozish o'rniga, biz shunchaki unga havola beramiz. Va tamom.

Anonim sinf va lambda ifodasi o'rtasidagi qiziqarli farq

Anonim sinfda kalit so'z thiso'sha anonim sinf ob'ektiga ishora qiladi. Va agar biz uni lambda ichida ishlatsak this, biz ramkalash sinfining ob'ektiga kirish imkoniyatiga ega bo'lamiz. Biz aslida bu iborani qayerda yozgan edik. Buning sababi, lambda iboralari kompilyatsiya qilinganda, ular yozilgan sinfning shaxsiy usuliga aylanadi. Men ushbu "xususiyatdan" foydalanishni tavsiya etmayman, chunki u funktsional dasturlash tamoyillariga zid bo'lgan yon ta'sirga ega. Ammo bu yondashuv OOP bilan juda mos keladi. ;)

Ma'lumotni qayerdan oldim yoki yana nimani o'qish kerak

Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION