Bu maqola kim 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.
Agar siz ushbu toifalardan biriga kirmasangiz, siz ushbu maqolani zerikarli, noto'g'ri va umuman "salqin emas" deb topishingiz mumkin. Bunday holda, bemalol o'tib keting yoki agar siz mavzuni yaxshi bilsangiz, sharhlarda maqolani qanday yaxshilash yoki to'ldirishni taklif qiling. Material hech qanday ilmiy ahamiyatga ega emas, yangilik kamroq. Aksincha, aksincha: unda men murakkab (ba'zilar uchun) narsalarni iloji boricha sodda tasvirlashga harakat qilaman. Men oqim api ni tushuntirish so'rovidan ilhomlantirdim. Men bu haqda o'yladim va lambda iboralarini tushunmasdan, "oqimlar" haqidagi ba'zi misollarim tushunarsiz bo'lishiga qaror qildim. Shunday qilib, keling, lambdalardan boshlaylik.
Ushbu maqolani tushunish uchun qanday bilim kerak:
- Ob'ektga yo'naltirilgan dasturlashni tushunish (keyingi o'rinlarda OOP deb yuritiladi), xususan:
- sinflar va ob'ektlar nima ekanligini bilish, ular o'rtasidagi farq nima;
- interfeyslar nima ekanligini, ular sinflardan qanday farq qilishini, ular o'rtasidagi bog'liqlikni bilish (interfeyslar va sinflar);
- usul nima ekanligini, uni qanday chaqirish kerakligini, mavhum usul (yoki amalga oshirilmagan usul) nima ekanligini, usulning parametrlari/argumentlari nima ekanligini, ularni u erga qanday o'tkazishni bilish;
- kirish modifikatorlari, statik usullar/o'zgaruvchilar, yakuniy usullar/o'zgaruvchilar;
- meros (sinflar, interfeyslar, interfeyslarning ko'p merosi).
- Java Core haqida ma'lumot: generiklar, to'plamlar (ro'yxatlar), mavzular.
Xo'sh, keling, boshlaylik.
Bir oz tarix
Lambda ifodalari Java-ga funktsional dasturlashdan, u erda esa matematikadan kelgan. 20-asrning o'rtalarida Amerikada Prinston universitetida matematikani va barcha turdagi abstraktsiyalarni juda yaxshi ko'radigan Alonzo cherkovi ishlagan. Aynan Alonzo cherkovi lambda hisobini o'ylab topdi, bu dastlab ba'zi mavhum g'oyalar to'plami bo'lib, dasturlash bilan hech qanday aloqasi yo'q edi. Shu bilan birga, Alan Turing va Jon fon Neyman kabi matematiklar o'sha Prinston universitetida ishlagan. Hamma narsa birlashdi: Cherch lambda hisoblash tizimini yaratdi, Tyuring o'zining mavhum hisoblash mashinasini yaratdi, hozirda "Tyuring mashinasi" deb nomlanadi. Xo'sh, fon Neumann zamonaviy kompyuterlarning asosini tashkil etuvchi kompyuterlar arxitekturasining diagrammasini taklif qildi (u hozir "von Neumann arxitekturasi" deb ataladi). O'sha paytda Alonzo Cherkovning g'oyalari uning hamkasblari ("sof" matematika sohasi bundan mustasno) ishi kabi shuhrat qozonmadi. Biroq, bir oz vaqt o'tgach, Jon Makkarti (shuningdek, Prinston universiteti bitiruvchisi, voqea sodir bo'lgan paytda - Massachusets texnologiya instituti xodimi) cherkov g'oyalari bilan qiziqib qoldi. Ularga asoslanib, 1958 yilda u Lisp nomli birinchi funksional dasturlash tilini yaratdi. Oradan 58 yil o'tib esa funksional dasturlash g'oyalari Java-ga 8-raqam sifatida kirib keldi. Oradan 70 yil ham o'tmadi... Aslida, bu matematik g'oyani amaliyotda qo'llash uchun eng uzoq vaqt emas.
mohiyati
Lambda ifodasi shunday funktsiyadir. Buni Java'da odatiy usul deb o'ylashingiz mumkin, yagona farq shundaki, uni boshqa usullarga argument sifatida o'tkazish mumkin. Ha, usullarga nafaqat raqamlar, satrlar va mushuklarni, balki boshqa usullarni ham o'tkazish mumkin bo'ldi! Bu bizga qachon kerak bo'lishi mumkin? Misol uchun, agar biz qayta qo'ng'iroq qilishni xohlasak. Biz unga o'tadigan boshqa usulni chaqirishimiz uchun biz chaqiradigan usulga muhtojmiz. Ya'ni, biz ba'zi hollarda bitta qayta qo'ng'iroqni, boshqalarida esa boshqasini uzatish imkoniyatiga ega bo'lishimiz uchun. Va bizning qayta qo'ng'iroqlarimizni qabul qiladigan usulimiz ularni chaqiradi. Oddiy misol - tartiblash. Aytaylik, biz shunga o'xshash qandaydir murakkab tartiblash yozamiz:
public void mySuperSort() {
if(compare(obj1, obj2) > 0)
}
Qaerda
if
usulni chaqiramiz
compare()
, u yerga solishtiradigan ikkita ob'ektni o'tkazamiz va biz ushbu ob'ektlarning qaysi biri "kattaroq" ekanligini aniqlamoqchimiz. Biz "ko'proq" bo'lganini "kichikroq" dan oldin qo'yamiz. Men qo'shtirnoq ichida "ko'proq" deb yozdim, chunki biz nafaqat o'sish, balki kamayish tartibida ham tartiblash imkoniyatiga ega bo'lgan universal usulni yozmoqdamiz (bu holda "ko'proq" ob'ekt aslida kichikroq bo'ladi va aksincha). . Aynan qanday saralashni istayotganimiz qoidasini o'rnatish uchun uni qandaydir tarzda o'zimizga o'tkazishimiz kerak
mySuperSort()
. Bunday holda, biz o'z usulimizni chaqirish paytida qandaydir tarzda "nazorat qila olamiz". Albatta, o'sish va kamayish tartibida tartiblashning
mySuperSortAsc()
ikkita alohida usulini yozishingiz mumkin.
mySuperSortDesc()
Yoki usul ichida ba'zi parametrlarni o'tkazing (masalan,
boolean
agar
true
, o'sish tartibida va agar
false
kamayish tartibida tartiblang). Agar biz oddiy tuzilmani emas, balki, masalan, qatorlar qatorlari ro'yxatini saralashni istasak nima bo'ladi? Bizning usulimiz
mySuperSort()
ushbu satr massivlarini qanday saralashni biladi? O'lchamga? So'zlarning umumiy uzunligi bo'yicha? Ehtimol, massivdagi birinchi qatorga qarab, alifbo tartibida? Ammo ba'zi hollarda massivlar ro'yxatini massivning o'lchamiga ko'ra, boshqa holatda esa massivdagi so'zlarning umumiy uzunligi bo'yicha tartiblashimiz kerak bo'lsa-chi? O'ylaymanki, siz taqqoslashlar haqida allaqachon eshitgansiz va bunday hollarda biz shunchaki solishtiruvchi ob'ektni saralash usulimizga o'tkazamiz, unda biz saralashni xohlagan qoidalarni tasvirlaymiz. Standart usul
sort()
xuddi shu printsip bo'yicha amalga oshirilganligi sababli,
mySuperSort()
misollarda men standartdan foydalanaman
sort()
.
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};
List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);
Comparator<String[]> sortByLength = new Comparator<String[]>() {
@Override
public int compare(String[] o1, String[] o2) {
return o1.length - o2.length;
}
};
Comparator<String[]> sortByWordsLength = new Comparator<String[]>() {
@Override
public int compare(String[] o1, String[] o2) {
int length1 = 0;
int length2 = 0;
for (String s : o1) {
length1 += s.length();
}
for (String s : o2) {
length2 += s.length();
}
return length1 - length2;
}
};
arrays.sort(sortByLength);
Natija:
- onam ramkani yuvdi
- tinchlik Mehnat may
- Men java-ni juda yaxshi ko'raman
Bu erda massivlar har bir massivdagi so'zlar soni bo'yicha tartiblanadi. Kamroq so'zli massiv "kichikroq" hisoblanadi. Shuning uchun u boshida keladi. Ko'proq so'z bo'lgan "ko'proq" deb hisoblanadi va oxirida tugaydi. Agar metodga
sort()
boshqa taqqoslagichni o'tkazsak
(sortByWordsLength)
,
natija boshqacha bo'ladi:
- tinchlik Mehnat may
- onam ramkani yuvdi
- Men java-ni juda yaxshi ko'raman
Endi massivlar shunday massiv so'zlaridagi harflarning umumiy soni bo'yicha tartiblanadi. Birinchi holda 10 ta, ikkinchisida 12 ta, uchinchisida 15 ta harf mavjud. Agar biz faqat bitta taqqoslash vositasidan foydalansak, u uchun alohida o'zgaruvchi yarata olmaymiz, shunchaki anonim sinf ob'ektini yaratamiz. usulni chaqirish vaqti
sort()
. Shunga o'xshash:
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};
List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);
arrays.sort(new Comparator<String[]>() {
@Override
public int compare(String[] o1, String[] o2) {
return o1.length - o2.length;
}
});
Natija birinchi holatda bo'lgani kabi bo'ladi. 1-topshiriq . Ushbu misolni shunday yozingki, u massivlarni massivdagi so'zlar sonining o'sish tartibida emas, balki kamayish tartibida tartiblaydi. Bularning barchasini biz allaqachon bilamiz. Biz ob'ektlarni usullarga o'tkazishni bilamiz, biz u yoki bu ob'ektni hozirgi vaqtda kerakli narsaga qarab usulga o'tkazishimiz mumkin va biz bunday ob'ektni o'tkazadigan usul ichida biz amalga oshirishni yozgan usul deyiladi. . Savol tug'iladi: lambda iboralarining bunga qanday aloqasi bor?
Lambda aynan bitta usulni o'z ichiga olgan ob'ekt ekanligini hisobga olsak. Bu usul ob'ektiga o'xshaydi. Ob'ektga o'ralgan usul. Ular biroz noodatiy sintaksisga ega (lekin bu haqda keyinroq). Keling, ushbu yozuvni yana bir bor ko'rib chiqaylik
arrays.sort(new Comparator<String[]>() {
@Override
public int compare(String[] o1, String[] o2) {
return o1.length - o2.length;
}
});
Bu erda biz ro'yxatimizni olamiz
arrays
va uning usulini chaqiramiz
sort()
, bu erda biz taqqoslash ob'ektini bitta usul bilan o'tkazamiz
compare()
(u nima deb nomlangani biz uchun muhim emas, chunki u ushbu ob'ektda yagona, biz uni o'tkazib yubormaymiz). Ushbu usul ikkita parametrni oladi, biz ular bilan keyingi ishlaymiz. Agar siz
IntelliJ IDEA da ishlasangiz , ehtimol u sizga ushbu kodni sezilarli darajada qisqartirish uchun qanday taklif qilayotganini ko'rgansiz:
arrays.sort((o1, o2) -> o1.length - o2.length);
Shunday qilib, olti satr bitta qisqa chiziqqa aylandi. 6 satr bitta qisqa chiziqqa qayta yozildi. Biror narsa g'oyib bo'ldi, lekin men hech qanday muhim narsa yo'qolmaganiga kafolat beraman va bu kod anonim sinf bilan bir xil ishlaydi.
Vazifa 2 . Lambdalar yordamida 1-muammoning yechimini qanday qayta yozishni aniqlang (oxirgi chora sifatida
IntelliJ IDEA- dan anonim sinfingizni lambdaga aylantirishni so'rang).
Keling, interfeyslar haqida gapiraylik
Asosan, interfeys faqat mavhum usullar ro'yxatidir. Biz sinf yaratganimizda va u qandaydir interfeysni amalga oshiradi desak, biz o'z sinfimizga interfeysda sanab o'tilgan usullarni amalga oshirishni yozishimiz kerak (yoki oxirgi chora sifatida uni yozmasdan, balki sinfni abstrakt qilishimiz kerak. ). Turli xil usullarga ega interfeyslar mavjud (masalan,
List
), faqat bitta usulga ega interfeyslar mavjud (masalan, bir xil Comparator yoki Runnable). Bitta usulsiz interfeyslar mavjud (marker interfeyslari deb ataladi, masalan, Serializable). Faqat bitta usulga ega bo'lgan interfeyslar
funktsional interfeyslar deb ham ataladi . Java 8 da ular hatto maxsus
@FunctionalInterface izohi bilan belgilangan . Bu lambda ifodalari bilan foydalanish uchun mos bo'lgan yagona usul bilan interfeyslar. Yuqorida aytganimdek, lambda ifodasi ob'ektga o'ralgan usuldir. Va bunday ob'ektni biron bir joyga o'tkazganimizda, biz, aslida, bitta usuldan o'tamiz. Ma'lum bo'lishicha, biz uchun bu usul qanday nomlanishi muhim emas. Biz uchun muhim bo'lgan narsa bu usul qabul qiladigan parametrlar va aslida usul kodining o'zi. Lambda ifodasi, aslida. funktsional interfeysni amalga oshirish. Bir usul bilan interfeysni ko'rsak, bu biz lambda yordamida bunday anonim sinfni qayta yozishimiz mumkinligini anglatadi. Agar interfeys bir nechta/kamroq usulga ega bo'lsa, u holda lambda ifodasi bizga mos kelmaydi va biz anonim sinfdan yoki hatto oddiy sinfdan foydalanamiz. Lambdalarni qazish vaqti keldi. :)
Sintaksis
Umumiy sintaksis quyidagicha:
(параметры) -> {тело метода}
Ya'ni, qavslar, ularning ichida usul parametrlari, "o'q" (bular ketma-ket ikkita belgi: minus va kattaroq), shundan so'ng usulning tanasi har doimgidek jingalak qavslarda joylashgan. Parametrlar usulni tavsiflashda interfeysda ko'rsatilganlarga mos keladi. Agar o'zgaruvchilar turini kompilyator aniq belgilashi mumkin bo'lsa (bizning holatda, biz satrlar massivlari bilan ishlayotganimiz aniq ma'lum, chunki u
List
satrlar massivlari bilan aniq terilgan), u holda o'zgaruvchilar turi
String[]
kerak emas . yozilsin.
Agar ishonchingiz komil bo'lmasa, turini belgilang, agar kerak bo'lmasa, IDEA uni kulrang rangda ajratib ko'rsatadi. |
Siz , masalan,
Oracle qo'llanmasida ko'proq o'qishingiz mumkin . Bu
"maqsadli yozish" deb ataladi . O'zgaruvchilarga har qanday nom berishingiz mumkin, interfeysda ko'rsatilganlar shart emas. Parametrlar bo'lmasa, faqat qavslar. Agar faqat bitta parametr bo'lsa, faqat qavssiz o'zgaruvchining nomi. Biz parametrlarni saralab oldik, endi lambda ifodasining o'zi haqida. Jingalak qavslar ichida oddiy usuldagi kabi kodni yozing. Agar butun kodingiz faqat bitta satrdan iborat bo'lsa, siz umuman jingalak qavslar yozishingiz shart emas (ifs va looplar kabi). Agar lambda biror narsani qaytarsa, lekin uning tanasi bitta chiziqdan iborat bo'lsa, uni
return
yozish kerak emas. Ammo agar sizda jingalak qavslar bo'lsa, unda odatdagi usulda bo'lgani kabi, siz aniq yozishingiz kerak
return
.
Misollar
1-misol.
() -> {}
Eng oddiy variant. Va eng ma'nosiz :) Chunki u hech narsa qilmaydi.
2-misol.
() -> ""
Bundan tashqari, qiziqarli variant. U hech narsani qabul qilmaydi va bo'sh qatorni qaytaradi (
return
keraksiz deb o'tkazib yuborilgan). Xuddi shunday, lekin bilan
return
:
() -> {
return "";
}
Misol 3. Salom dunyo lambdalar yordamida
() -> System.out.println("Hello world!")
Hech narsa qabul qilmaydi, hech narsa qaytarmaydi (biz
return
qo'ng'iroqdan oldin qo'yib bo'lmaydi
System.out.println()
, chunki usulda qaytish turi
println() — void)
ekranda oddiygina yozuvni ko'rsatadi. Interfeysni amalga oshirish uchun ideal
Runnable
. Xuddi shu misol to'liqroq:
public class Main {
public static void main(String[] args) {
new Thread(() -> System.out.println("Hello world!")).start();
}
}
Yoki shunday:
public class Main {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("Hello world!"));
t.start();
}
}
Yoki biz lambda ifodasini ob'ekt turi sifatida saqlashimiz
Runnable
va keyin uni konstruktorga o'tkazishimiz mumkin
thread’а
:
public class Main {
public static void main(String[] args) {
Runnable runnable = () -> System.out.println("Hello world!");
Thread t = new Thread(runnable);
t.start();
}
}
Keling, lambda ifodasini o'zgaruvchiga saqlash momentini batafsil ko'rib chiqaylik. Interfeys
Runnable
bizga uning ob'ektlari usuli bo'lishi kerakligini aytadi
public void run()
. Interfeysga ko'ra, ishga tushirish usuli hech narsani parametr sifatida qabul qilmaydi. Va hech narsa qaytarmaydi
(void)
. Shuning uchun, shu tarzda yozilayotganda, hech narsani qabul qilmaydigan yoki qaytarmaydigan ba'zi bir usul bilan ob'ekt yaratiladi.
run()
Bu interfeysdagi usulga juda mos keladi
Runnable
. Shuning uchun biz ushbu lambda ifodasini kabi o'zgaruvchiga qo'yishga muvaffaq bo'ldik
Runnable
.
4-misol
() -> 42
Shunga qaramay, u hech narsani qabul qilmaydi, lekin 42 raqamini qaytaradi. Ushbu lambda ifodasi turidagi o'zgaruvchiga joylashtirilishi mumkin
Callable
, chunki bu interfeys faqat bitta usulni belgilaydi, u quyidagicha ko'rinadi:
V call(),
V
Qaytish qiymatining turi qayerda (bizning holatlarimizda
int
). Shunga ko'ra, biz lambda ifodasini quyidagicha saqlashimiz mumkin:
Callable<Integer> c = () -> 42;
Misol 5. Lambda bir necha qatorda
() -> {
String[] helloWorld = {"Hello", "world!"};
System.out.println(helloWorld[0]);
System.out.println(helloWorld[1]);
}
Shunga qaramay, bu parametrlarsiz va uning qaytish turisiz lambda ifodasidir
void
(chunki mavjud emas
return
).
6-misol
x -> x
Bu erda biz biror narsani o'zgaruvchiga olamiz
х
va uni qaytaramiz. E'tibor bering, agar faqat bitta parametr qabul qilingan bo'lsa, uning atrofidagi qavslar yozilishi shart emas. Xuddi shunday, lekin qavslar bilan:
(x) -> x
Va bu erda aniq variant mavjud
return
:
x -> {
return x;
}
Yoki bu kabi, qavslar bilan va
return
:
(x) -> {
return x;
}
Yoki turini aniq ko'rsatgan holda (va shunga mos ravishda qavslar bilan):
(int x) -> x
7-misol
x -> ++x
Biz buni qabul qilamiz
х
va qaytarib beramiz, lekin
1
ko'proq. Siz uni quyidagicha qayta yozishingiz mumkin:
x -> x + 1
Ikkala holatda ham parametr, usul tanasi va so'z atrofida qavs ko'rsatmaymiz
return
, chunki bu shart emas. Qavslar va qaytarishli variantlar 6-misolda tasvirlangan.
8-misol
(x, y) -> x % y
Biz bir qismini qabul qilamiz
х
va
у
bo'linishning qolgan qismini
x
bilan qaytaramiz
y
. Parametrlar atrofidagi qavslar bu erda allaqachon talab qilinadi. Ular faqat bitta parametr mavjud bo'lganda ixtiyoriydir. Turlarning aniq ko'rsatilishi bilan shunday:
(double x, int y) -> x % y
9-misol
(Cat cat, String name, int age) -> {
cat.setName(name);
cat.setAge(age);
}
Biz Cat ob'ektini, nomi va butun soniga ega qatorni qabul qilamiz. Usulning o'zida biz o'tkazilgan ism va yoshni Mushukga o'rnatdik.
cat
Bizning o'zgaruvchimiz mos yozuvlar turi bo'lganligi sababli, lambda ifodasi tashqarisidagi Cat ob'ekti o'zgaradi (u ichida kiritilgan ism va yoshni oladi). Shunga o'xshash lambda ishlatadigan biroz murakkabroq versiya:
public class Main {
public static void main(String[] args) {
Cat myCat = new Cat();
System.out.println(myCat);
Settable<Cat> s = (obj, name, age) -> {
obj.setName(name);
obj.setAge(age);
};
changeEntity(myCat, s);
System.out.println(myCat);
}
private static <T extends WithNameAndAge> void changeEntity(T entity, Settable<T> s) {
s.set(entity, "Murzik", 3);
}
}
interface WithNameAndAge {
void setName(String name);
void setAge(int age);
}
interface Settable<C extends WithNameAndAge> {
void set(C entity, String name, int age);
}
class Cat implements WithNameAndAge {
private String name;
private int age;
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Natija: Cat{name='null', age=0} Cat{name='Murzik', age=3} Ko'rib turganingizdek, dastlab Cat obyekti bitta holatga ega edi, lekin lambda ifodasidan foydalangandan so'ng holat o'zgardi . Lambda ifodalari generiklar bilan yaxshi ishlaydi. Va agar biz sinfni yaratishimiz kerak bo'lsa
Dog
, masalan, u ham amalga oshiradi
WithNameAndAge
, u holda usulda
main()
biz lambda ifodasini o'zgartirmasdan, xuddi shu amallarni Dog bilan bajarishimiz mumkin.
3-topshiriq . Raqamni oladigan va mantiqiy qiymatni qaytaradigan usul bilan funktsional interfeysni yozing.
true
O'tkazilgan son 13 ga qoldiqsiz bo'linadigan bo'lsa, qaytaradigan lambda ifodasi ko'rinishidagi bunday interfeysning amalga oshirilishini yozing.4
-topshiriq . Ikki qatorni oladigan va bir xil satrni qaytaradigan usul bilan funktsional interfeysni yozing. Eng uzun satrni qaytaradigan lambda ko'rinishida bunday interfeysning amalga oshirilishini yozing.
5-topshiriq . Funktsional interfeysni uchta kasr sonni qabul qiladigan usul bilan yozing:
a
,
b
,
c
va bir xil kasr sonini qaytaradi. Diskriminantni qaytaruvchi lambda ifodasi ko'rinishida bunday interfeysning amalga oshirilishini yozing. Kim unutdi,
D = b^2 - 4ac .
6-topshiriq . 5-topshiriqdagi funktsional interfeysdan foydalanib, operatsiya natijasini qaytaruvchi lambda ifodasini yozing
a * b^c
.
Java'da lambda ifodalari haqida mashhur. Misollar va topshiriqlar bilan. 2-qism.
GO TO FULL VERSION