1. Funksional interfeysə daha dərindən baxış
Funksional interfeys — elə bir interfeysdir ki, onda DƏQİQ olaraq bir abstrakt (yəni reallaşdırılmamış) metod var. Məhz buna görə Java anlayır: «aha, bura lambda yerləşdirmək olar!» və ya metoda istinad etmək olar.
Kimsə səhv etməsin deyə, belə interfeyslərdə adətən @FunctionalInterface annotasiyası qoyulur. Bu məcburi deyil, amma onu əlavə edib təsadüfən ikinci abstrakt metod yazsanız, kompilyator dərhal etiraz edəcək.
Nümunə:
@FunctionalInterface
interface MyAction {
void run();
}
MyAction action = () -> System.out.println("Lambdadan salam!");
action.run(); // Çap edəcək: Lambdadan salam!
Bu nə üçün lazımdır?
- Anonim siniflər yaratmaq əvəzinə lambda ifadələrini və metod istinadlarını istifadə etməyə imkan verir (daha az boilerplate!).
- Kompilyatora interfeysin funksional proqramlaşdırma üçün nəzərdə tutulduğunu başa düşməyə kömək edir.
Maraqlı fakt: Java-nın standart kitabxanasında artıq onlarla belə interfeys var — velosipedi yenidən icad etməyə ehtiyac yoxdur!
2. Standart funksional interfeyslərə ümumi baxış
java.util.function paketində onlarla funksional interfeys “yaşayır”. Gəlin ən populyar dördünü nəzərdən keçirək (bunlar bütün Java-interfeysləri arasında ən çox istifadə olunanlardır).
| Interfeys | Nə qəbul edir | Nə qaytarır | Adətən nə üçün istifadə olunur |
|---|---|---|---|
|
|
|
Şərtin yoxlanması (filtrləmə) |
|
|
|
Obyekt üzərində əməliyyat icra etmək |
|
heç nə | |
Obyekti almaq/qenerasiya etmək |
|
|
|
T-ni R-ə çevirmək |
Predicate<T>
Təsvir: T tipində obyekt qəbul edib true və ya false qaytaran funksiya. Tipik nümunə: siyahının filtrlənməsi. Açar metod — test.
Predicate<String> isLong = s -> s.length() > 5;
System.out.println(isLong.test("Java")); // false
System.out.println(isLong.test("Functional")); // true
Consumer<T>
Təsvir: T tipində obyekt qəbul edir və onunla əməliyyat aparır, heç nə qaytarmır. Açar metod — accept.
Consumer<String> printer = s -> System.out.println("Çap edirəm: " + s);
printer.accept("Hello, world!"); // Çap edirəm: Hello, world!
Supplier<T>
Təsvir: Heç nə qəbul etmir, T tipində obyekt qaytarır. Dəyərlərin «generatoru» kimi təsəvvür etmək olar. Açar metod — get.
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get()); // Məsələn, 0.1234567
Function<T, R>
Təsvir: T tipində obyekt qəbul edir və R tipində obyekt qaytarır. Tipik nümunə: məlumatların çevrilməsi. Açar metod — apply.
Function<String, Integer> stringToLength = s -> s.length();
System.out.println(stringToLength.apply("Java")); // 4
Qısa olaraq: UnaryOperator, BinaryOperator, BiFunction
- UnaryOperator<T> — Function<T, T> ilə eynidir: eyni tipi qəbul edir və eyni tip qaytarır.
- BinaryOperator<T> — BiFunction<T, T, T> ilə eynidir: iki T qəbul edir, bir T qaytarır.
- BiFunction<T, U, R> — iki fərqli tip qəbul edir, üçüncü tipi qaytarır.
UnaryOperator<Integer> square = x -> x * x;
BinaryOperator<Integer> sum = (a, b) -> a + b;
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
3. İstifadə nümunələri
Gəlin bu interfeyslərin real tapşırıqlarda və xüsusilə — kolleksiyalarda və Stream API-də necə istifadə olunduğuna baxaq.
Kolleksiya metodlarına və Stream API-yə ötürmə
Nümunə 1: Predicate və filtrləmə
List<String> words = List.of("java", "stream", "lambda", "code");
List<String> longWords = words.stream()
.filter(word -> word.length() > 4) // Predicate<String>
.toList();
System.out.println(longWords); // [stream, lambda]
Nümunə 2: Consumer və forEach
words.forEach(word -> System.out.println("Söz: " + word)); // Consumer<String>
Nümunə 3: Function və map
List<Integer> lengths = words.stream()
.map(word -> word.length()) // Function<String, Integer>
.toList();
System.out.println(lengths); // [4, 6, 6, 4]
Nümunə 4: Supplier və dəyərlərin generasiyası
Supplier<String> greetingSupplier = () -> "Salam, Java!";
System.out.println(greetingSupplier.get()); // Salam, Java!
Anonim siniflərlə müqayisə
Əvvəllər bunu belə yazmalı idik:
Predicate<String> isShort = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() < 5;
}
};
Lambdalarla bu xeyli daha rahat oldu:
Predicate<String> isShort = s -> s.length() < 5;
4. Təcrübə: hər interfeys üçün lambda ifadələri yazırıq
Kiçik bir tətbiq həyata keçirək — istifadəçi siyahısı. Hər bir istifadəçi User sinfi ilə təmsil olunacaq:
public class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
İstifadəçilər siyahısı yaradaq:
List<User> users = List.of(
new User("Anna", 23),
new User("Boris", 17),
new User("Vika", 31),
new User("Gosha", 15)
);
Predicate: böyüklərin filtrasiyası
Predicate<User> isAdult = user -> user.getAge() >= 18;
List<User> adults = users.stream()
.filter(isAdult)
.toList();
System.out.println("Böyüklər: " + adults); // Böyüklər: [Anna (23), Vika (31)]
Consumer: istifadəçilərin çapı
Consumer<User> printUser = user -> System.out.println("İstifadəçi: " + user);
adults.forEach(printUser);
Supplier: istifadəçilərin generasiyası
Supplier<User> randomUserSupplier = () -> {
String[] names = {"Dima", "Katya", "Lyosha"};
int randomAge = 10 + (int)(Math.random() * 30);
String randomName = names[(int)(Math.random() * names.length)];
return new User(randomName, randomAge);
};
User randomUser = randomUserSupplier.get();
System.out.println("Təsadüfi istifadəçi: " + randomUser);
Function: istifadəçinin adını almaq
Function<User, String> getName = user -> user.getName();
List<String> names = users.stream()
.map(getName)
.toList();
System.out.println("Adlar: " + names); // Adlar: [Anna, Boris, Vika, Gosha]
5. Faydalı məqamlar
Stream API-də istifadə: filter, map, forEach və s.
Hər şeyi bir yerə toplayıb çevirmə zənciri yazaq:
users.stream()
.filter(user -> user.getAge() >= 18) // Predicate<User>
.map(user -> user.getName().toUpperCase()) // Function<User, String>
.forEach(name -> System.out.println("Böyük: " + name)); // Consumer<String>
Nəticə:
Böyük: ANNA
Böyük: VIKA
Tez-baxa cədvəl: hara nə qoymaq lazımdır
| Harada istifadə olunur | Hansı interfeys lazımdır | İstifadə nümunəsi |
|---|---|---|
| filter (Stream) | |
|
| map (Stream) | |
|
| forEach (Stream, List) | |
|
| generate (Stream) | |
|
Funksional interfeysləri bilmək niyə vacibdir?
- Onlar Java-da bütün lambda ifadələrinin əsasını təşkil edir.
- Universal, yenidən istifadə oluna bilən və yığcam kod yazmağa imkan verir.
- Kolleksiyalar, axınlar (streams), asinxron tapşırıqlarla işi sadələşdirir.
Hansı interfeysi nə vaxt istifadə etməli?
- Predicate — şərt yoxlamaq lazım olanda (filtrləmək, axtarmaq).
- Consumer — obyektlə nəsə etmək lazım olanda (çıxışa vermək, yazmaq, göndərmək).
- Supplier — obyekt əldə etmək və ya qenerasiya etmək lazım olanda (fabriklər, generatorlar).
- Function — obyekti bir tipdən digərinə çevirmək lazım olanda.
6. Funksional interfeyslərlə işləyərkən tipik səhvlər
Səhv №1: Interfeysin düzgün seçilməməsi. Bəzən yeni başlayanlar Predicate ilə Function-u səhv salırlar — məsələn, Function-dan boolean qaytarmağa çalışırlar, halbuki bunu Predicate etməlidir. Yadda saxlayın: Predicate həmişə boolean qaytarır, Function — digər istənilən tipi.
Səhv №2: Standart interfeyslərdən istifadə etməmək. Tez-tez Predicate əvəzinə boolean check(T t) metodu olan «Checker» kimi öz interfeyslərini yazırlar. Standartlardan istifadə etmək daha yaxşıdır — onlar hər yerdə dəstəklənir və kodu digər proqramçılar üçün daha anlaşıqlı edir.
Səhv №3: Lambda həddindən artıq mürəkkəbdir. Lambda 10 sətirdən ibarət mini-romana çevrilirsə, onu ayrıca metoda və ya sinfə çıxarmağa dəyər. Lambda qısalıq və oxunaqlılıq üçündür.
Səhv №4: @FunctionalInterface annotasiyasının unudulması. Öz funksional interfeysinizi yazırsınızsa — annotasiyanı unutmayın. O, təsadüfi səhvlərdən (məsələn, ikinci abstrakt metodun əlavə edilməsi) sığortalayacaq.
Səhv №5: Lambda daxilində dəyişən vəziyyətdən istifadə. Lambda xarici dəyişənləri və ya kolleksiyaları dəyişirsə, bu, xüsusən axınlarla işləyərkən gözlənilməz xətalara səbəb ola bilər. Yan təsirlərdən qaçmaq daha yaxşıdır.
GO TO FULL VERSION