CodeGym /Kurslar /JAVA 25 SELF /Funksiyaların kompozisiyası: compose, andThen

Funksiyaların kompozisiyası: compose, andThen

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

1. Funksiyaların kompozisiyası anlayışı

Bir az nəzəriyyə (amma çox da yorucu deyil)

Riyaziyyatda funksiyaların kompozisiyası — bir funksiyanın nəticəsinin digər funksiyanın girişi olmasıdır. Əgər bizdə f və g funksiyaları varsa, g(f(x)) kompozisiyası belədir: əvvəlcə f funksiyasını x-ə tətbiq edirik, sonra nəticəni g-yə veririk.

Proqramlaşdırmada ideya eynidir: mürəkkəb çevrilmələri sadə funksiyalardan yığmaq istəyirik ki, hər şey üçün nəhəng bir funksiyanı yazmayaq. Bu, kodu çevik, yenidən istifadə oluna bilən və oxunaqlı edir.

Şirniyyat konveyerini təsəvvür edin: əvvəl xəmir (f), sonra krem (g), sonra bəzək səpintisi (h). Bütöv proses — h(g(f(inqrediyentlər))).

Niyə kompozisiya vacibdir?

  • Kompozisiya proqramı kiçik funksiyalardan ibarət “kublar”dan yığmağa imkan verir.
  • Yenidən istifadə rahatdır: bir “kubu” təkrarlamadan müxtəlif yerlərə qoşa bilərsiniz.
  • Elastiklik: hər hansı mərhələni dəyişmək lazım olduqda müvafiq funksiyanı əvəz edirik — qalanına toxunmuruq.
  • Oxunaqlıq və test edilə bilmə: kiçik funksiyaları oxumaq, yoxlamaq və dəstəkləmək daha asandır.

2. compose və andThen metodları Function interfeysində

Function interfeysi: xatırlatma

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    // Kompozisiya üçün metodlar:
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
}
  • compose: əvvəlcə compose-ə verdiyiniz funksiya icra olunur, sonra — cari funksiya.
  • andThen: əvvəlcə cari funksiya icra olunur, sonra — andThen-ə verilən funksiya.

Vizual sxem

// Tutaq ki, iki funksiya var:
Function<String, Integer> parse = s -> Integer.parseInt(s);
Function<Integer, Integer> square = x -> x * x;

// compose: square.compose(parse) == x -> square.apply(parse.apply(x))
"5" --parse--> 5 --square--> 25

// andThen: parse.andThen(square) == x -> square.apply(parse.apply(x))
"5" --parse--> 5 --square--> 25

// Amma tiplər fərqlidirsə, sıra önəmlidir!

Nümunə: sətirdən ədədə, sonra kvadrata çevirmə

import java.util.function.Function;

public class ComposeAndThenDemo {
    public static void main(String[] args) {
        // Funksiya: sətiri ədədə çevirir
        Function<String, Integer> parse = s -> Integer.parseInt(s);

        // Funksiya: ədədi kvadrata yüksəldir
        Function<Integer, Integer> square = x -> x * x;

        // Kombinasiya: əvvəl parse, sonra kvadrata yüksəlt
        Function<String, Integer> parseThenSquare = parse.andThen(square);

        System.out.println(parseThenSquare.apply("7")); // 49

        // Əgər yerləri dəyişsək?
        // square.compose(parse) — eyni nəticə (bu funksiyalar üçün)
        Function<String, Integer> squareOfParsed = square.compose(parse);

        System.out.println(squareOfParsed.apply("8")); // 64
    }
}

Sıra nə vaxt əhəmiyyət kəsb edir?

Əgər funksiyaların tipləri uyğun gəlmirsə, sıra kritik olur. Məsələn:

Function<String, String> addPrefix = s -> "User: " + s;
Function<String, Integer> length = s -> s.length();

Function<String, Integer> composed = addPrefix.andThen(length);
System.out.println(composed.apply("Alice")); // "User: Alice" -> 11

// Bəs belə:
// length.andThen(addPrefix) — kompilyasiya xətası!
// length Integer qaytarır, addPrefix isə String qəbul edir.

Cədvəl: compose və andThen arasındakı fərq

f
g
f.compose(g)
f.andThen(g)
T → R
V → T
V → R: f(g(x))
T → V: g(f(x))

3. Predikatların və digər interfeyslərin kompozisiyası

Predicate<T>: and, or, negate

Funksional interfeys Predicate<T> — nəticəsi boolean olan funksiyadır. Predikatları birləşdirmək üçün xüsusi metodlar var:

  • and: məntiqi VƏ (&&)
  • or: məntiqi VEYA (||)
  • negate: məntiqi DEYİL (!)

Nümunə: mürəkkəb filtr şərtləri

Tutaq ki, bizdə istifadəçi sinfi var:

public class User {
    String name;
    int age;

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

İndi bir neçə predikat yazaq:

import java.util.function.Predicate;

Predicate<User> isAdult = user -> user.age >= 18;
Predicate<User> nameStartsWithA = user -> user.name.startsWith("A");

// Kombinasiya: yetkin və adı "A" ilə başlayan
Predicate<User> adultAndA = isAdult.and(nameStartsWithA);

// Ya yetkin, ya da adı "A" ilə başlayan
Predicate<User> adultOrA = isAdult.or(nameStartsWithA);

// Yetkin deyil
Predicate<User> notAdult = isAdult.negate();

İndi bu predikatları filtrasiya üçün, məsələn, Stream API ilə istifadə edə bilərik:

import java.util.List;
import java.util.stream.Collectors;

List<User> users = List.of(
    new User("Alice", 20),
    new User("Bob", 17),
    new User("Anna", 15),
    new User("Mike", 22)
);

List<User> filtered = users.stream()
    .filter(adultAndA)
    .collect(Collectors.toList());

// filtered siyahısına yalnız Alice düşəcək (yetkindir və adı "A" ilə başlayır)

Consumer, Function, Supplier kompozisiyası

  • Consumer<T>: andThen metodu — iki əməliyyatı ard-arda icra etməyə imkan verir.
  • Function<T, R>: kompozisiyanı yuxarıda nəzərdən keçirdik.
  • Supplier<T>: birbaşa birləşdirilmir, amma digər funksiyaların içində istifadə oluna bilər.

Nümunə: Consumer<T>.andThen

import java.util.function.Consumer;

Consumer<String> print = s -> System.out.println("Alındı: " + s);
Consumer<String> printUpper = s -> System.out.println("Böyük hərflərlə: " + s.toUpperCase());

Consumer<String> combined = print.andThen(printUpper);

combined.accept("hello");
// Çıxış:
// Alındı: hello
// Böyük hərflərlə: HELLO

4. Təcrübə: çevrilmə və filtrasiya zəncirləri

Tapşırıq 1: Function ilə çevrilmə zənciri qurmaq

Tutaq ki, tətbiqimizdə istifadəçiləri "Ad,Yaş" sətiri kimi saxlayırıq, məsələn "Alice,20". Tələb olunur:

  • Sətiri User obyektinə çevirmək
  • Yaşı əldə etmək
  • İstifadəçinin yetkin olub-olmadığını yoxlamaq
import java.util.function.Function;
import java.util.function.Predicate;

Function<String, User> stringToUser = str -> {
    String[] parts = str.split(",");
    return new User(parts[0], Integer.parseInt(parts[1]));
};
Function<User, Integer> getAge = user -> user.age;
Predicate<Integer> isAdultAge = age -> age >= 18;

// Kombinasiya: sətir -> User -> yaş -> predikat
Function<String, Integer> stringToAge = stringToUser.andThen(getAge);

String input = "Bob,19";
int age = stringToAge.apply(input);
System.out.println("Yaş: " + age); // 19

System.out.println("Yetkindirmi? " + isAdultAge.test(age)); // true

Tapşırıq 2: Filtrasiya üçün bir neçə Predicate-i birləşdirmək

Tutaq ki, bizə 18-dən böyük və adı "A" və ya "M" ilə başlayan istifadəçiləri seçmək lazımdır.

Predicate<User> isAdult = user -> user.age > 18;
Predicate<User> nameStartsWithA = user -> user.name.startsWith("A");
Predicate<User> nameStartsWithM = user -> user.name.startsWith("M");

Predicate<User> filter = isAdult.and(nameStartsWithA.or(nameStartsWithM));

List<User> filtered = users.stream()
    .filter(filter)
    .collect(Collectors.toList());

Tapşırıq 3: Çoxmərhələli Function çevrilməsi

Tutaq ki, lazımdır: sətiri götürmək, boşluqları kəsmək, böyük hərflərə çevirmək, "USER: " prefiksi əlavə etmək.

Function<String, String> trim = String::trim;
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> addPrefix = s -> "USER: " + s;

// Zənciri qururuq
Function<String, String> pipeline = trim.andThen(toUpper).andThen(addPrefix);

System.out.println(pipeline.apply("   vasya   ")); // USER: VASYA

5. Funksiyaların kompozisiyasında tipik səhvlər

Xəta №1: compose/andThen ardıcıllığının qarışdırılması.
Yeni başlayanlar tez-tez əvvəl və sonra nə icra olunduğunu qarışdırırlar. Yadda saxlayın: f.compose(g) — əvvəl g, sonra f; f.andThen(g) — əvvəl f, sonra g.

Xəta №2: Tiplərin uyğun gəlməməsi.
Bir funksiyanın nəticə tipi digərinin parametr tipinə uyğun gəlmirsə — kompilyator birləşdirməyə icazə verməyəcək. Məsələn, Function<Integer, String>.andThen(Function<Double, Boolean>) etmək olmaz.

Xəta №3: Həddən artıq mürəkkəb zəncirlər.
Bəzən bütün biznes məntiqini bir zəncirə sığışdırmaq və nəticədə “spagetti” əldə etmək istənilir. Zənciri kiçik funksiyalara bölün və onlara anlaşılan adlar verin.

Xəta №4: Funksiyalarda yan təsirlər.
Funksiya və predikatları “təmiz” saxlamaq daha yaxşıdır (yan təsirlər olmadan), əks halda kompozisiya təhlükəli və qeyri-müəyyən olur.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Gizli Cəmiyyət üçün istifadəçi adlarının standartlaşdırılması 🕵️‍♂️
Gizli Cəmiyyət üçün istifadəçi adlarının standartlaşdırılması 🕵️‍♂️
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Sehrli ədədlərin çevrilməsi Sehrlər Məktəbində 🧙‍♀️
Sehrli ədədlərin çevrilməsi Sehrlər Məktəbində 🧙‍♀️
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION