CodeGym /Kurslar /JAVA 25 SELF /Funksiyaların parametr kimi ötürülməsi: nümunələr

Funksiyaların parametr kimi ötürülməsi: nümunələr

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

1. Məlumat əvəzinə davranışın ötürülməsi

Ümumiyyətlə funksiyanı parametr kimi ötürmək niyə lazımdır?

Təsəvvür edin: sizdə siyahını çeşidləyən bir metod var. Bəs o, necə çeşidləməyi haradan bilsin? Əlifbaya görə? Uzunluğa görə? Doğum tarixinə görə? Hər hal üçün ayrıca metod yazmaq olardı, amma bu, tezliklə dəhşətli kopipestə çevriləcək.

Bunun əvəzinə Java sizə davranışı (yəni funksiyanı və ya lambdanı) ötürməyə imkan verir; o, necə çeşidləməyi, filtrləməyi, çevirməyi və s. müəyyən edir. Bu, kodu çevik və yenidən istifadə oluna bilən edir.

Nümunə: Comparator ilə çeşidləmə

List<String> names = List.of("Anna", "Boris", "Vika");

// Davranış ötürürük: elementləri necə müqayisə etməli (sətir uzunluğuna görə)
names.stream()
     .sorted((a, b) -> a.length() - b.length())
     .forEach(System.out::println);

Nümunə: Predicate ilə filtrasiya

List<String> names = List.of("Anna", "Boris", "Vika");

// Davranış ötürürük: kimi saxlamalı (adın uzunluğu 4 simvoldan böyükdür)
names.stream()
     .filter(name -> name.length() > 4)
     .forEach(System.out::println);

Hər iki halda siz məntiqi metoda sərt şəkildə “tikmədiniz”, əksinə ona hər bir elementə tətbiq ediləcək kiçik bir davranış verdiniz.

Faydası: daha az təkrarlama, daha çox çeviklik

Oxşar kodlu, lakin fərqli məntiqi olan onlarla metod əvəzinə, funksiyanı “nə etməli” kimi qəbul edən bir universal metod yazırsınız. Bu, vaxtınıza qənaət edir, səhvləri azaldır və kodu test etməyi asanlaşdırır.

2. Funksiyaların ötürülməsi sintaksisi

Lambda-ifadələr arqument kimi

Ən çox yayılmış üsul — elə metodun çağırıldığı yerdəcə ox -> ilə lambdanı yazmaqdır:

list.forEach(item -> System.out.println(item));

Hətta daha qısa ola bilər, əgər uyğun metod artıq varsa — metod istinadı (::):

list.forEach(System.out::println);

Metod istinadları (method references)

Əgər sizdə artıq uyğun metod varsa, ona :: kimi istinad ötürmək olar:

// Adi metod
public static boolean isLongName(String name) {
    return name.length() > 4;
}

// Metoda istinadı ötürmək
names.stream()
     .filter(MyClass::isLongName)
     .forEach(System.out::println);

Bu, metodun imzası funksional interfeysdə gözlənilənlə (Predicate<T>, Function<T, R>, Comparator<T> və s.) uyğun gəlirsə işləyir.

3. Standart kitabxanadan nümunələr

Collections.sort və Comparator

List<String> names = new ArrayList<>(List.of("Anna", "Boris", "Vika"));

// Adın uzunluğuna görə çeşidləmə
names.sort((a, b) -> a.length() - b.length());
System.out.println(names); // [Vika, Anna, Boris]

Stream.filter və Predicate

List<String> names = List.of("Anna", "Boris", "Vika");

// Yalnız 'V' ilə başlayan adları saxlayın
names.stream()
     .filter(name -> name.startsWith("V"))
     .forEach(System.out::println); // Vika

Stream.map və Function

List<String> names = List.of("Anna", "Boris", "Vika");

// Adları böyük hərflərə çevirmək
names.stream()
     .map(String::toUpperCase)
     .forEach(System.out::println);

Optional.ifPresent və Consumer

Optional<String> opt = Optional.of("Salam!");

// Əgər dəyər varsa, onu çap et
opt.ifPresent(s -> System.out.println("Sətirdə: " + s));

4. Təcrübə: funksiya-parametrləri ilə öz metodlarımızı yazırıq

Bilgiləri praktikada tətbiq etməyin vaxtıdır! Gəlin, funksiyanı parametr kimi qəbul edən və onu daxilində istifadə edən bir neçə metod yazaq.

Nümunə 1: Siyahı elementlərini işləyən metod (forEach öz üslubumuzla)

Tutaq ki, bizdə istifadəçilər siyahısı var (User). Hər bir istifadəçi üzərində hansısa əməliyyatı yerinə yetirmək istəyirik — məsələn, adı çıxışa vermək, e-mail göndərmək, bonus yazmaq və s. Həmin əməliyyatı sərt şəkildə “tikmək” əvəzinə, onu parametr kimi — Consumer<User> — ötürək!

import java.util.List;
import java.util.function.Consumer;

class User {
    String name;
    int age;
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class UserProcessor {
    // Consumer<User> qəbul edən metod
    public static void processUsers(List<User> users, Consumer<User> action) {
        for (User user : users) {
            action.accept(user);
        }
    }

    public static void main(String[] args) {
        List<User> users = List.of(
            new User("Anna", 25),
            new User("Boris", 30),
            new User("Vika", 22)
        );

        // Bütün istifadəçilərin adlarını çıxışa ver
        processUsers(users, user -> System.out.println("Ad: " + user.name));

        // Bonusu yaz (nümunə üçün sadəcə çıxışa veririk)
        processUsers(users, user -> System.out.println(user.name + " bonus aldı!"));
    }
}

Nümunə 2: Elementlərin filtrasiyası üçün metod (Predicate)

Şərtə uyğun gələn istifadəçiləri (məsələn, yalnız yetkinləri) qaytaran metod yazaq:

import java.util.List;
import java.util.ArrayList;
import java.util.function.Predicate;

public class UserFilter {
    public static List<User> filterUsers(List<User> users, Predicate<User> condition) {
        List<User> result = new ArrayList<>();
        for (User user : users) {
            if (condition.test(user)) {
                result.add(user);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        List<User> users = List.of(
            new User("Anna", 25),
            new User("Boris", 17),
            new User("Vika", 22)
        );

        // Yalnız yetkinləri filtr edirik
        List<User> adults = filterUsers(users, user -> user.age >= 18);
        adults.forEach(user -> System.out.println(user.name)); // Anna, Vika
    }
}

Nümunə 3: Çevirici metod (Function)

İndi isə istifadəçilər siyahısını onların adları siyahısına (və ya istənilən başqa çevirməyə) çevirən metod:

import java.util.List;
import java.util.ArrayList;
import java.util.function.Function;

public class UserMapper {
    public static <R> List<R> mapUsers(List<User> users, Function<User, R> mapper) {
        List<R> result = new ArrayList<>();
        for (User user : users) {
            result.add(mapper.apply(user));
        }
        return result;
    }

    public static void main(String[] args) {
        List<User> users = List.of(
            new User("Anna", 25),
            new User("Boris", 17),
            new User("Vika", 22)
        );

        // Adların siyahısını alırıq
        List<String> names = mapUsers(users, user -> user.name);
        System.out.println(names); // [Anna, Boris, Vika]

        // Yaşların siyahısını alırıq
        List<Integer> ages = mapUsers(users, user -> user.age);
        System.out.println(ages); // [25, 17, 22]
    }
}

Nümunə 4: Generator metod (Supplier)

Bəzən “tələb üzrə” hansısa dəyəri almaq lazımdır — məsələn, təsadüfi ədəd generasiya etmək, obyekt yaratmaq, cari vaxtı əldə etmək. Bunun üçün Supplier<T> interfeysi uyğundur.

import java.util.function.Supplier;

public class ValueGenerator {
    public static int getValue(Supplier<Integer> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        // Təsadüfi ədəd alırıq
        int random = getValue(() -> (int)(Math.random() * 100));
        System.out.println("Təsadüfi ədəd: " + random);

        // Cari vaxtı millisaniyələrlə alırıq
        long time = getValue(System::currentTimeMillis);
        System.out.println("Vaxt: " + time);
    }
}

5. Vahid tətbiq: nümunələri birləşdiririk

Tutaq ki, biz sadə “İstifadəçilər siyahısı” tətbiqi hazırlayırıq. Artıq bacarırıq:

  • İstifadəçiləri şərtə görə filtr etmək (məsələn, yalnız yetkinlər);
  • İstifadəçiləri nəyəsə çevirmək (ad, e-mail, yaş);
  • Hər istifadəçi üzərində hərəkətlər icra etmək (çap, bonus yazmaq);
  • Tələb üzrə dəyərlər generasiya etmək (məsələn, yeni istifadəçilər yaratmaq).

İndi bu metodlardan istifadə etməklə, kodu hər dəfə yeni tapşırığa görə yenidən yazmadan çevik verilənlər emalı ssenariləri qura bilərik.

import java.util.*;
import java.util.function.*;

public class UserApp {
    static class User {
        String name;
        int age;
        User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String toString() {
            return name + " (" + age + ")";
        }
    }

    // Universal metod: istifadəçiləri emal etmək
    static void processUsers(List<User> users, Consumer<User> action) {
        for (User user : users) action.accept(user);
    }

    // Universal filtr
    static List<User> filterUsers(List<User> users, Predicate<User> condition) {
        List<User> result = new ArrayList<>();
        for (User user : users) if (condition.test(user)) result.add(user);
        return result;
    }

    // Universal çevirici
    static <R> List<R> mapUsers(List<User> users, Function<User, R> mapper) {
        List<R> result = new ArrayList<>();
        for (User user : users) result.add(mapper.apply(user));
        return result;
    }

    public static void main(String[] args) {
        List<User> users = List.of(
            new User("Anna", 25),
            new User("Boris", 17),
            new User("Vika", 22)
        );

        // 1. Bütün istifadəçiləri çıxışa ver
        processUsers(users, user -> System.out.println("İstifadəçi: " + user));

        // 2. Yalnız yetkinləri tap
        List<User> adults = filterUsers(users, user -> user.age >= 18);
        System.out.println("Yetkinlər: " + adults);

        // 3. Yetkinlərin adlarını al
        List<String> adultNames = mapUsers(adults, user -> user.name);
        System.out.println("Yetkinlərin adları: " + adultNames);

        // 4. Yetkinlərə bonus yaz
        processUsers(adults, user -> System.out.println(user.name + " bonus aldı!"));
    }
}

Bu niyə “adi” yanaşmadan daha yaxşıdır?

Bu üsul çeviklik verir: eyni metodları tamamilə fərqli ssenarilərdə, sadəcə başqa funksiyalar ötürərək istifadə edə bilirsiniz. Nəticədə kod “belə filtr edirik”, “elə çap edirik” kimi sonsuz variantlara parçalanmır — hər yerdə işləyən ümumi alətlərimiz var. Bu sayədə o, daha yığcam, anlaşılan və səhvlərə daha az meylli olur. Üstəlik, bu üslub müasir Stream API ilə əla uyğunlaşır, ona görə də axınlara keçəndə yenidən öyrənməyə ehtiyac qalmır — yanaşma eynidir.

6. Funksiyaları parametr kimi ötürərkən tipik səhvlər

Səhv № 1: İmza gözlənilən interfeysə uyğun gəlmir.
Əgər metod Predicate<User> qəbul edirsə, sizsə boolean yox, məsələn, String qaytaran lambda ötürürsünüzsə, kompilyator dərhal “ay-ay-ay!” deyəcək və layihəni yığmağa icazə verməyəcək. Qaytarılan dəyərin və parametrlərin gözlənilənlə uyğun gəldiyini yoxlayın.

Səhv № 2: Lambda dəyişə bilən dəyişənlərdən istifadə edir.
Java-da lambda-ifadələr yalnız xarici kontekstdən final və ya effektiv olaraq final olan dəyişənləri istifadə edə bilər. Belə dəyişəni lambdanın içində dəyişməyə çalışsanız — kompilyasiya xətası olacaq.

Səhv № 3: Interfeyslər arasında qarışıqlıq.
Bəzən məsələn, Consumer<T> ötürmək istəyirsiniz, amma təsadüfən nəsə qaytaran funksiya yazırsınız (məsələn, Function<T, R>). Lambda-nızın lazım olanı qaytardığını (və ya heç nə — Consumer üçün) yoxlayın.

Səhv № 4: Parametrlərdə həddən artıq mürəkkəb lambdalar.
Əgər lambda bir-iki sətirdən artıqdırsa, onu ayrıca dəyişənə və ya metoda çıxarmaq daha yaxşıdır. Əks halda kod oxunmaz olacaq və onda yalnız siz baş çıxaracaqsınız (o da həmişə yox).

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