Daha öncəki məqalədə yazdığımız lambdas nümunəyə baxaq:
a -> a.canHop()
Bu o deməkdir ki, bu anonim metod Animal
class tipində parametr qəbul edir və geriyə boolean dəyər döndürür (result of a.canHop()
). Bütün bunları biz özümüz bilirik, çünki kodu biz yazmışıq, bəs java bunları necə bilir?
Lambda ifadəsi görəndə yada salınmalı olan ilk şey bir interface`dir, çünki lambda ifadələri hər zaman bir interface`ə bağlı olaraq çalışır. Bunlar funksional interface adlanır və bunların mütləq bir abstract metodu olmalıdır. (interface`dən öncə @FunctionalInterface
annotasiyası yazılır, amma istəyə bağlıdır, yazılmaya da bilər). Birdən çox olduqda compile xətası verir. Bəs lambda ifadəsi interface ilə necə əlaqələndirilir? İlk öncə bunu başa düşmək mənə də çətin idi, çünki praktikada mütəmadi istifadə etdiyimiz bir anlayış deyil, mahiyyətini dərindən dərk etmək zaman alır. Hər şeyi oxuyub başa düşürdüm, amma müəyyən bir müddət sonra yenidən bu mövzuya qayıtdıqda oxuduqlarımı yenidən unutduğumu görürdüm, səbəb – praktikada tətbiq etməmə. Bir az kodla pratik etdikdən sonra bunu bir üsulla yadda saxlamaq qərarına gəldim, ümid edirəm bu üsul sizin üçün də faydalı olar, baxaq.
Əvvəlki mövzuda da qeyd etdik ki, biz lambdas`a adsız metod kimi də baxa bilərik. Daha diqqətlə fikir versək görərik ki, bu əslində funksional interface`dəki metodun override
olunmuş formasıdır. Addım-addım baxaq:
1) Qeyd etdik ki, lambda ifadəsi mütləq bir funksional interface`ə bağlı olur və biz parametr olaraq lambda ifadəsini hansı metoda göndəririksə, həmin metod da parametr olaraq funksional interface`in referansını qəbul edir. Əvvəlki mövzudakı kod nümunəsinə fikir verək:
print(animals, a -> a.canHop());
Biz print()
metoduna ikinci parametr olaraq lambda ifadəsi göndəririk. print()
metodunun strukturuna baxsaq görərik ki, ikinci parametr olaraq Checker
interface`i tipində parametr qəbul edir. Burada Checker
funksional interface`dir.
private static void print(List<Animal> animals, Checker checker)
2) İndi isə Checker
interface`nin abstract metoduna fikir verməliyik. Qeyd etdik ki, funksional interface olduğu üçün ancaq və ancaq bir abstract metodu ola bilər. Aha, indi biz qeyd etdiyimiz məqama gəldik çıxdıq. Deməli bizim lambda ifadəsi ancaq bu abstract metoda uyğun olmalıdır, başqa seçimimiz yoxdur. Başqa sözlə həmin abstract metodu parametr olaraq göndərilən lambda ifadəsinin içində override
edir.
3) Bəs necə override edir? Gəlin abstract metodumuzu və lambda ifadəmizi alt-alta yazaq və müqayisə edək:
boolean test(Animal a);
(Animal a) -> { return a.canHop(); }
Abstract metodda hər şey aydındı Animal
classı tipində parametr qəbul edir və geriyə boolean
dəyər döndürür. Normal qaydada bu metodu aşağıdakı şəkildə override edə bilərik:
@Override public boolean test(Animal a) { return a.canHop(); }
İndi keçək lambda ifadəmizə. Hər şey aydın olsun deyə əvvəlcə lambda ifadəsinin sintaksisinə baxaq. Bütün lambda ifadələri üç hissədən ibarət olur: parametr listi, ox (arrow) və gövdə (body). Ox işarəsi parametr listi ilə gövdəni ayırır.
Qısa formada*:
Tam formada*:
Bəzi hissələr optional`dı, tam formada da yazmaq olar, qısa formada da. İndi baxaq bizim nümunəni hansı formalarda yazmaq mümkündür:
a -> a.canHop() (a) -> a.canHop() (Animal a) -> a.canHop() (Animal a) -> { return a.canHop(); }
Solda mötərizə ancaq o halda yazılmaya bilər ki, parametr sayı bir ədəd olsun və onun tipi aşkar şəkildə göstərilməsin. Sağda fiqurlu mötərizə ancaq o halda buraxıla bilər ki, ifadələrin sayı ancaq bir ədəd olsun (only have a single statement). Əgər fiqurlu mötərizə yoxdursa, biz nə return
ifadəsini, nə də nöqtəli-vergülü (;
) yaza bilmərik. Və həmçinin nöqtəli-vergülü yazıb return
ifadəsini yazmamaq da mümkün deyil, əgər bunlar yazılacaqsa ikisi də yazılmalıdır (burada bir istisna var, əgər funksional interfeysdəki metodun tipi void
olarsa, o zaman nöqtəli-vergül yazılır, return
yazılmır, return
yazılarsa compile xətası verər). Qısaca əgər fiqurlu mötərizə varsa bunlar da mütləq olmalıdır, fiqurlu mötərizə yoxdursa bunlar da olmamalıdır. Əks halda compile xətası verir. Əgər ifadələrin sayı iki və daha çox olarsa fiqurlu mötərizə mütləq olmalıdır.
Valid lambdas:
print( () -> true); // 0 parameters print( a -> a.startsWith("test")); // 1 parameter print( (String a) -> a.startsWith("test")); // 1 parameter print( (a, b) -> a.startsWith("test")); // 2 parameters print( (String a, String b) -> a.startsWith("test")); // 2 parameters
Invalid lambdas:
print( a, b -> a.startsWith("test")); // needs parentheses print( a -> { a.startsWith("test"); }); // missing return keyword print( a -> { return a.startsWith("test") }); // missing semicolon print( a -> { a.startsWith("test") }); // both missing print( a -> return a.startsWith("test"); ); // there is no braces
Verilmiş nümunələrdə ancaq geriyə boolean dəyər döndürən lambdas nümunələri göstərilib, çünki OCA imtahanı üçün bunu öyrənmək yetərlidir.
Indi qayıdaq abstract metod ilə lambda ifadəsinin əlaqəsinə.
Mövzunun əvvəlində axtardığımız “Bəs java bunları necə bilir?” sualının cavabı bu şəkildən aydın görünür. Deməli lambda ifadəsinin:
- parametr listi abstract metodun parametr listi ilə eyni olmalıdır;
- geri döndürüyü dəyər abstract metodun tipi ilə eyni olmalıdır.
Bu şəkili çəkib göstərməkdə məqsədim odur ki, lambda ifadəsinin gövdə hissəsini abstract metodun gövdə hissəsinə yerləşdirsək bir növü onu override
etmiş olarıq. Ümid edirəm bu formada yanaşıldıqda daha rahat başa düşülər.
Aşağıdakı formada kod nümunələrinə imtahan suallarında tez-tez rast gələ bilərsiniz, bu zaman yadınıza salın ki, java eyni adlı local dəyişən elan etməyə icazə vermir:
(a, b) -> { int a = 0; return 5; } // DOES NOT COMPILE
(a, b) -> { int c = 0; return 5; }
Bu nümunədə ayniadlı dəyişənlər eyni sətirdə olduğundan bunu tutmaq daha rahatdır, yəni diqqətimizi cəlb edir. Amma hərdən elə nümunələr ola bilər ki (Enthuware testlərində dəqiq biri var), dəyişən adı metod daxilində ya parametr listində yaxud da daha öncəki sətirlərdə elan edilə bilər. Və sonradan həmin dəyişən adı çaşdırmaq üçün lambda sintaksisində parametr listində yenidən istifadə edilə bilər. Buna diqqət etmək lazımdır.
İmtahan mövzusuna daxil edilməsə də bilməkdə fayda var ki, lambda ifadələrində digər dəyişənlərdən də istifadə etməyə icazə verilir, lakin istisnalar var. Static və instance dəyişənləri ilə hər şey qaydasındadır. Amma metod parametrləri və local dəyişənləri ancaq o halda istifadə etmək mümkündür ki, onlara yeni dəyər mənimsədilməmiş olsun:
class Test { String s1 = "test"; static String s2 = "test"; public static void main(String[] args) { String s3, s4 = "test"; s3 = "test"; print(a -> a.startsWith(new Test().s1)); print(a -> a.startsWith(s2)); print(a -> a.startsWith(s3)); s4 = "test"; print(a -> a.startsWith(s4)); // DOES NOT COMPILE } private static void print(FunkInterface inf) { String s = "test"; if (inf.test(s)) ; } interface FunkInterface { boolean test(String a); } }
MyExamCloud test nümunələrindən birində maraqlı bir sualla rastlaşmışdım. Sualda soruşulurdu ki, aşağıdakı interface`lərdən hansılar functional interface hesab edilə bilər:
interface A<R> extends B { static void method() { } } interface B<T> { public void print(T t); static void print() { } } interface C { void methodC(String s); } interface D<T> extends A, B, C { default void printer(T t) { } }
Mövzunun əvvəlində qeyd etmişdik ki, funksional interfeyslərin mütləq bir abstract metodu olmalıdır, birdən çox olduqda compile xətası verir. Amma bu qayda sırf abstract metodlara aiddir, interfeysin daxilində default
və static
metodlar ola bilər və bunlar interfeysin funksional olma özəlliyini pozmur.
Bu qaydalara istinadən birbaşa deyə bilərik ki, B
və C
interfeysləri funksional interfeyslərdir. A
interfeysinin abstract metodu olmasa da göründüyü kimi B
interfeysindən törəyir və B
interfeysinin abstract print(T t)
metodunu varis alır. Məhz bu səbəbdən A
da funksional interfeys hesab edilir. D
interfeysi sintaksis olaraq düzgün olsa da funksional interfeys hesab edilə bilməz. Çünki həm B
, həm də C
interfeyslərinin abstract metodlarını varis alır və abstract metodlarının sayı birdən çox olduğu üçün funksional interfeys hesab edilmir.
Növbəti məqalədə Predicates
interfeysi ilə tanış olacağıq.
[topics lang=az]