1. Tanışlıq
Qısaca desək: lambda ifadəsi — ayrıca sinif və ya anonim sinif elan etmədən, “yerindəcə” funksional interfeysin reallaşdırmasını tez yaratmağın bir yoludur. Bu, adı olmayan mini-metod kimidir; onu arqument kimi ötürmək və ya dəyişəndə saxlamaq olar.
Java uzun müddət “klassik” OOP dili idi. Əgər davranış parçasını ötürmək istəyirdinizsə, məsələn, düyməyə basıldıqda nə etmək lazım olduğunu, anonim siniflər yazmalı idiniz:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("Düymə basıldı!");
}
});
Java 8 ilə lambda sehrləri yarandı:
button.setOnClickListener(() -> System.out.println("Düymə basıldı!"));
Burada () -> System.out.println("Düymə basıldı!") — bu elə lambda ifadəsidir.
Rəsmi olaraq: lambda ifadəsi — funksional interfeysin yeganə abstrakt metodunun reallaşdırılmasının yığcam yazılış formasıdır.
Niyə bu vacibdir?
- Davranışı dəyər kimi ötürmək (məsələn, siyahının hər elementi ilə nə etmək).
- Yığcam və oxunaqlı kod yazmaq.
- Java-nın müasir API-lərindən istifadə: Stream API, hadisələrin emalı, asinxron tapşırıqlar və daha çoxu.
2. Lambda ifadələrinin sintaksisi
Ümumi forma
(parametrlər) -> ifadə
// və ya
(parametrlər) -> { kod bloku }
Müxtəlif interfeyslər üçün nümunələr
1. Parametrsiz (məsələn, Runnable):
Runnable r = () -> System.out.println("Salam, lambda!");
r.run();
2. Bir parametr (məsələn, Consumer):
Consumer<String> printer = s -> System.out.println("Siz ötürdünüz: " + s);
printer.accept("Java");
Parametr yalnız bir dənədirsə və tipini çıxarmaq mümkündürsə, mötərizələri buraxmaq olar.
String tipi — ora ötürdüyümüz dəyişənin (s) tipidir.
3. Bir neçə parametr (məsələn, Comparator):
Comparator<Integer> cmp = (a, b) -> a - b;
System.out.println(cmp.compare(10, 5)); // 5
Integer tipi — ora ötürdüyümüz dəyişənlərin (a, b) tipidir.
4. Çoxsətirli bədən (açıq qaytarma varsa süslü mötərizə və return gərəkdir):
Function<Integer, Integer> square = x -> {
int result = x * x;
return result;
};
System.out.println(square.apply(6)); // 36
Birinci Integer — funksiyanın nəticə tipidir, ikinci Integer — parametrin tipidir.
Qısaltmalar və yığcamlıq
- Əgər gövdə tək ifadədən ibarətdirsə, mötərizələri və return-u buraxmaq olar.
- Parametr yoxdursa — boş mötərizə yazırıq: () -> ...
- Bir parametr varsa — mötərizəsiz ola bilər: x -> ...
- Bir neçə parametr olduqda — mötərizə lazımdır: (a, b) -> ...
Cədvəl-xülasə
Eyni ideyanın lambda və anonim siniflə necə yazıldığına baxaq:
| İnterfeys | Lambda ifadəsi (nümunə) | Anonim sinifin ekvivalenti |
|---|---|---|
|
|
|
|
|
|
|
|
|
Öz interfeysimizlə nümunə
Tutaq ki, bizdə funksional interfeys var:
@FunctionalInterface
interface Operation {
int apply(int a, int b);
}
Əvvəllər belə həyata keçirilirdi:
Operation sum = new Operation() {
@Override
public void apply(int a, int b) {
return a + b;
}
};
İndi — müasir yanaşma:
Operation sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5)); // 8
Standart interfeyslər üçün nümunələr
Runnable:
Runnable hello = () -> System.out.println("Hello from thread!");
new Thread(hello).start();
Comparator:
List<String> list = Arrays.asList("alma", "banan", "kivi");
list.sort((a, b) -> a.length() - b.length());
System.out.println(list);
Function:
Function<String, Integer> parse = s -> Integer.parseInt(s);
System.out.println(parse.apply("123")); // 123
3. Görünürlük sahəsi və dəyişənlərin tutulması
Xarici kontekstdən olan dəyişənlər (effectively final)
Lambda ifadələri əhatə edən metoddan dəyişənlərdən istifadə edə bilər. Amma bir qayda var: bu cür dəyişənlər effectively final olmalıdır — yəni ya açıq şəkildə final kimi elan edilir, ya da sadəcə ilkinləşmədən sonra dəyişdirilmir.
Nümunə:
String prefix = "Nəticə: ";
Function<Integer, String> f = x -> prefix + (x * 2);
// prefix burada 'dondurulur' — bundan sonra onu dəyişmək olmaz
System.out.println(f.apply(5)); // Nəticə: 10
Əgər prefix-i lambda daxilində istifadə etdikdən sonra dəyişdirməyə cəhd etsəniz, kompilyator səhv verəcək.
Niyə belədir?
Lambda ifadəsi onun elan olunduğu metoddan çıxdıqdan sonra da çağırıla bilər. “Sehrli” buglardan qaçmaq üçün Java yalnız dəyişməyən dəyişənlərin istifadəsinə icazə verir.
Anonim siniflərdən fərqi
Anonim siniflərdə də eyni qayda keçərlidir: xarici metoddan götürülən dəyişənlər final/effectively final olmalıdır. Amma görünürlük sahəsi ilə bağlı nüanslar var: anonim sinifin içində this həmin anonim sinifə istinad edir, lambda-da isə — xarici sinifə.
public class Demo {
public void test() {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println(this); // Çap edəcək: Demo$1 (anonim sinif)
}
};
Runnable r2 = () -> System.out.println(this); // Çap edəcək: Demo (xarici sinif)
r1.run();
r2.run();
}
}
Lambda və sinif sahələri
Lambda ifadəsi xarici sinifin sahələrinə məhdudiyyətsiz müraciət edə bilər:
public class Counter {
private int base = 10;
public void printSum(int x) {
Function<Integer, Integer> sum = y -> base + y + x;
System.out.println(sum.apply(5));
}
}
Burada base — dəyişilə bilər (bu, sinif sahəsidir).
x — effectively final olmalıdır.
4. Təcrübə: bir neçə lambda ifadəsi yazaq
Nümunə: ədədlər siyahısının filtrasiyası
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
nums.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> System.out.println("Cüt: " + n));
Stream API haqqında daha ətraflı 30-cu səviyyədə öyrənəcəksiniz :P
Nümunə: sətirin çevrilməsi funksiyası
Function<String, String> capitalize = s -> s.toUpperCase();
System.out.println(capitalize.apply("java")); // JAVA
Nümunə: öz funksional interfeysiniz
@FunctionalInterface
interface StringTransformer {
String transform(String s);
}
StringTransformer exclaim = s -> s + "!";
System.out.println(exclaim.transform("Salam")); // Salam!
Nümunə: xarici kontekstdən dəyişənlərin istifadəsi
int factor = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * factor));
// Bundan sonra factor-u dəyişmək olmaz!
5. Lambda ifadələri ilə işləyərkən tipik səhvlər
Səhv №1: lambdanın tutduğu dəyişəni dəyişməyə cəhd.
Bu cür kod kompilyasiya olunmayacaq — dəyişən effectively final olmalıdır:
int sum = 0;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> sum += n); // Səhv: sum final deyil!
Əgər dəyərləri toplamaq lazımdırsa — massivdən və ya wrapper obyektdən istifadə edin.
Səhv №2: this-in görünürlük sahəsi ilə çaşqınlıq.
Lambda daxilində this xarici sinifə istinad edir, lambda-ya yox (anonim sinifdən fərqli olaraq).
Səhv №3: çoxsətirli lambda ifadəsində süslü mötərizələri və return-u unutmaq.
Əgər lambda gövdəsi tək ifadə deyiləcəksə, mötərizələr və return tələb olunur:
Function<Integer, Integer> square = x -> {
int y = x * x;
return y;
};
Səhv №4: lambda ifadəsinin tipini səhv təyin etmək.
Lambda ifadəsi həmişə funksional interfeysi reallaşdırır. Sadəcə belə yazmaq olmaz:
var f = x -> x + 1; // Səhv! Hansı interfeys tipi olduğu məlum deyil.
Tipi açıq şəkildə göstərmək lazımdır:
Function<Integer, Integer> f = x -> x + 1;
GO TO FULL VERSION