CodeGym /Kurslar /JAVA 25 SELF /Funksional interfeyslər: Predicate, Consumer, Supplier, F...

Funksional interfeyslər: Predicate, Consumer, Supplier, Function

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

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
Predicate<T>
T
boolean
Şərtin yoxlanması (filtrləmə)
Consumer<T>
T
void
Obyekt üzərində əməliyyat icra etmək
Supplier<T>
heç nə
T
Obyekti almaq/qenerasiya etmək
Function<T, R>
T
R
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)
Predicate<T>
filter(u -> u.getAge() > 18)
map (Stream)
Function<T, R>
map(u -> u.getName())
forEach (Stream, List)
Consumer<T>
forEach(u -> System.out.println(u))
generate (Stream)
Supplier<T>
Stream.generate(() -> ...)

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.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION