1. Giriş
Düzünü deyək: bir-iki sətirlik kod üçün anonim siniflər yazmaq — sanki çörəkxanadan mağazaya bir bulka aparmaq üçün nəhəng yük maşını kirayələmək kimidir.
Məsələn, sətirləri uzunluğa görə çeşidləmək istəsəniz, Java 8-dən əvvəl belə yazmalı idiniz:
List<String> list = Arrays.asList("alma", "banan", "kivi");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Görünən odur ki, vəzifə sadədir, amma kod — yarım ekran. Xüsusən bu cür əməliyyatlar çox olanda qıcıqlandırır: kod “səs-küylü” olur, vəzifənin mahiyyəti itib gedir. Proqramçılar əziyyət çəkdilər və sonra lambda ifadələrini icad etdilər. Biz artıq bir az öyrənmişik, indi isə biliklərimizi təkrar edib dərinləşdirəcəyik.
Lambda ifadəsi nədir
Lambda ifadəsi — funksional interfeysin (yəni cəmi bir abstrakt metodu olan interfeysin; məsələn, Comparator, Runnable, Consumer və s.) reallaşdırılmasının yığcam yazılış formasıdır.
Daha sadə desək, lambda ifadəsi ayrıca sinif və ya metod elan etmədən, bir funksiyanı elə lazım olduğu yerdəcə “sürətlə” yazmağa imkan verir.
Ümumi sintaksis:
(parametrlər) -> { gövdə }
Nümunələr:
- (a, b) -> a + b — iki ədədin toplanması funksiyası
- x -> x * x — ədədin kvadratını alma funksiyası
- () -> System.out.println("Hello!") — parametrlərsiz funksiya
Funksional interfeyslərlə əlaqə:
Lambda ifadəsini hər zaman funksional interfeys tipli dəyişənə mənimsətmək və ya həmin interfeysi gözləyən metoda arqument kimi ötürmək olar.
2. Lambda ifadələrinin sintaksisi
Parametrsiz
Runnable r = () -> System.out.println("Salam, dünya!");
r.run(); // Çap edəcək: Salam, dünya!
Bir parametr
Əgər parametr bir dənədirsə, mötərizəni atmaq olar:
Consumer<String> print = s -> System.out.println(s);
print.accept("Java əladır!");
Bir neçə parametr
Mötərizələr mütləqdir:
Comparator<String> cmp = (a, b) -> a.length() - b.length();
Bir ifadədən ibarət gövdə
Əgər gövdə bir ifadədən ibarətdirsə, süslü mötərizələr və return lazım deyil:
Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25
Blokdan ibarət gövdə
Bir neçə təlimat lazımdırsa, süslü mötərizələrdən və (qaytarılan nəticə varsa) return-dən istifadə edin:
Function<Integer, Integer> abs = x -> {
if (x < 0) {
return -x;
}
return x;
};
System.out.println(abs.apply(-3)); // 3
Parametrlərin tipləri
Əksər hallarda parametrlərin tipini göstərməyə ehtiyac yoxdur — kompilyator kontekstdən özü anlayacaq. Amma istəsəniz, açıq şəkildə yaza bilərsiniz:
Comparator<String> cmp = (String a, String b) -> a.length() - b.length();
Qaytarma dəyəri olmayan lambda
Əgər interfeys void qaytarırsa, sadəcə təlimatları yazın:
list.forEach(s -> System.out.println("Element: " + s));
Cədvəl: Lambda ifadələrinin sintaksis variantları
| Nə edirik | Nümunə | Şərh |
|---|---|---|
| Parametrsiz | |
Məsələn, Runnable üçün |
| Bir parametr | |
Mötərizəsiz mümkündür |
| Bir neçə parametr | |
Mütləq mötərizə |
| Tək ifadə | |
return və süslü mötərizələrsiz |
| Kod bloku | |
Nəticə varsa, return ilə |
3. Tətbiq: lambda ifadələrini harada və necə istifadə etməli
Lambda ifadələri ən çox “davranışı” — funksiyanı — arqument kimi ötürmək lazım olduqda istifadə edilir. Bu, kolleksiyalar, axınlar (Stream API), hadisələr və daha çox şey üçün əsl inqilab oldu.
Siyahının çeşidlənməsi
Java 8-dən əvvəl:
list.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Lambda ilə:
list.sort((a, b) -> a.length() - b.length());
Axının (Thread) yaradılması
Thread t = new Thread(() -> System.out.println("Axın işə düşdü!"));
t.start();
Kolleksiyaların emalı
list.forEach(s -> System.out.println(s.toUpperCase()));
Siyahının filtrasiyası
List<String> longWords = list.stream()
.filter(s -> s.length() > 5)
.collect(Collectors.toList());
Nümunə: Tədris tətbiqimizi inkişaf etdiririk
Tutaq ki, istifadəçi siyahımız var:
List<String> users = Arrays.asList("Alice", "Bob", "Charlie");
Adı 4 simvoldan uzun olan bütün istifadəçiləri çıxarmaq:
users.stream()
.filter(name -> name.length() > 4)
.forEach(name -> System.out.println("İstifadəçi: " + name));
4. Lambda ifadələrində dəyişənlərin görünürlük sahəsi
Lambda ifadələri əhatə edən metoddan dəyişənlərdən istifadə edə bilər, amma bəzi nüanslar var!
Dəyişənlər və lambdalar
Java-da lambda yalnız ilkinləşdirmədən sonra daha dəyişdirilməyən dəyişənləri “tuta” bilər. Əgər dəyişən final ilə elan olunubsa — hər şey aydındır. Amma söz final yazılmasa belə, kompilyator özü yoxlayır: dəyər dəyişir, ya yox. Dəyər dəyişmirsə, onu “guya final” hesab edir və ləngimədən lambda daxilində istifadə etməyə icazə verir.
Nümunə:
int minLength = 4; // dəyər heç yerdə dəyişmir
users.forEach(name -> {
if (name.length() > minLength) {
System.out.println(name);
}
});
Burada hər şey işləyir, çünki minLength eyni ədəd olaraq qalır.
Amma lambdada istifadə etdikdən sonra minLength-ə yenidən dəyər verməyə çalışsanız, kompilyasiya xətası alacaqsınız:
int minLength = 4;
users.forEach(name -> {
if (name.length() > minLength) {
System.out.println(name);
}
});
minLength = 10; // Xəta! Lambda artıq dəyəri "sabitlədi"
Mahiyyətcə qayda çox sadədir: lambdada istifadə olunan dəyişən dəyişməz olmalıdır.
Anonim siniflərdən fərqi
Anonim siniflərdə və lambda ifadələrində xarici metoddan gələn dəyişənlər eyni cür işləyir: yalnız final/effektiv final.
AMMA!
Lambda ifadəsində this xarici obyektə (sinifin cari nümunəsinə), anonim sinifdə isə anonim sinifin öz nümunəsinə istinad edir. Bu, lambdanın içində cari sinfin sahə və metodlarına müraciət edirsinizsə, mühümdür.
Nümunə:
public class Example {
String name = "Xarici sinif";
void demo() {
Runnable r1 = new Runnable() {
String name = "Anonim sinif";
@Override
public void run() {
System.out.println(this.name); // "Anonim sinif"
}
};
Runnable r2 = () -> System.out.println(this.name); // "Xarici sinif"
r1.run();
r2.run();
}
}
5. Lambda ifadələri ilə işləyərkən tipik səhvlər
Xəta №1: final/effektiv final olmayan dəyişənin istifadəsi. Lambdada istifadə etdikdən sonra dəyişəni dəyişməyə qərar versəniz, kompilyator dərhal sizi saxlayacaq. Bu, təhlükəsizlik üçün edilir: əks halda hansı dəyərdən istifadə olunacağı aydın olmazdı.
Xəta №2: this ilə qarışıqlıq. Lambda ifadəsində this — xarici sinifdir, anonim sinifdə isə — anonim sinifin özüdür. Əgər lambdadan xarici sinfin metodunu çağırmağa çalışırsınızsa, hər şey işləyəcək, anonim sinifdən isə — yox (əgər xarici sinfin kontekstinə güvənirdinizsə).
Xəta №3: Kontekstsiz lambda. Lambda ifadəsini təkbaşına istifadə etmək olmaz — onu ya funksional interfeys tipli dəyişənə mənimsətməli, ya da həmin interfeysi gözləyən yerə ötürməlisiniz. Sadəcə x -> x + 1 yazmaq kontekstdən kənar olduqda xəta verəcək.
Xəta №4: Həddən artıq mürəkkəb lambda. Lambda ifadəsi 3–5 sətirdən uzun olursa, oxumaq çətinləşir. Belə hallarda məntiqi ayrıca metoda çıxarmaq daha yaxşıdır.
GO TO FULL VERSION