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 counter
bo'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 counter
turdagi 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: counter
dastur 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 Runnable
va 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, Consumer
unga 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 void
harakat qiladi . void
forEach()
Consumer
Usul havolalaridan foydalanish sintaksisi
Bu juda oddiy:-
Statik usulga havolani o'tkazish
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 } }
-
Mavjud ob'ekt yordamida statik bo'lmagan usulga havolani o'tkazish
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 } }
-
Biz statik bo'lmagan usulga havolani bunday usul amalga oshirilgan sinfdan foydalanib o'tkazamiz
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); } } }
-
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'zthis
o'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
- Oracle rasmiy veb-saytida qo'llanma . Ko'p, batafsil, misollar bilan, lekin ingliz tilida.
- Xuddi shu "Oracle" qo'llanmasi , lekin bob usul havolalari haqida.
- Funktsional dasturlash haqida Habré haqida maqola (boshqa maqolaning tarjimasi). Lambdalar haqida ko'p kitoblar juda kam, chunki ular umuman funktsional dasturlash haqida.
- Vikipediyaga yopishib qolishni yaxshi ko'radiganlar uchun .
- Va, albatta, men Googleda juda ko'p narsalarni topdim :)
GO TO FULL VERSION