CodeGym /Kurslar /JAVA 25 SELF /Java-da closure-lar (closures): xüsusiyyətlər

Java-da closure-lar (closures): xüsusiyyətlər

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

1. Closure-larla tanışlıq

Closure — bu elə bir funksiya (və ya obyekt-funksiya)dır ki, yalnız öz parametrlərindən istifadə etmir, həm də yaradıldığı əhatə kontekstindən dəyişənləri «yada saxlayır». Sadə dildə desək, əgər metodun daxilindəki lambda ifadəsi və ya anonim sinif həmin metoddan dəyişənlərdən istifadə edirsə — o, closure-a çevrilir.

Sadə nümunə

public class ClosureDemo {
    public static void main(String[] args) {
        String greeting = "Salam, ";
        Runnable sayHello = () -> System.out.println(greeting + "dünya!");
        sayHello.run(); // Çap edəcək: Salam, dünya!
    }
}

Burada lambda xarici metoddan greeting dəyişənini «tutub» və onu öz daxilində istifadə edir. Bu, məhz closure-dur!

2. Effektiv final dəyişənlər: nədir və nə üçün?

Java-da lambda ifadələri (və anonim siniflər) yalnız belə dəyişənlərdən istifadə edə bilər: ya final kimi elan olunmuş, ya da ilkinləşdirmədən sonra dəyişdirilməyən. Belə dəyişənlərə effektiv final deyilir.

Niyə belədir?

Bu məhdudiyyət ona görə var ki, lambda ifadəsi onun yaradıldığı metod başa çatdıqdan sonra da çağırıla bilər. Əgər dəyişən dəyişdirilə bilsəydi, qarışıqlıq yaranardı: dəyişənin hansı versiyasından istifadə etməli? Gözlənilməzliklərin qarşısını almaq üçün Java tələb edir ki, dəyişən dəyişməz olsun (və ya ən azı dəyişməz kimi görünsün).

Nümunə: düzgün istifadə

public static void main(String[] args) {
    int number = 42; // number — effektiv final-dır
    Runnable r = () -> System.out.println(number);
    r.run(); // 42
}

Nümunə: istifadə olunduqdan sonra dəyişəni dəyişməyə cəhd

public static void main(String[] args) {
    int number = 42;
    Runnable r = () -> System.out.println(number);
    number++; // SƏHV: number dəyişəni final və ya effektiv final olmalıdır
    r.run();
}

Kompilyator belə bir səhv verəcək: Variable used in lambda expression should be final or effectively final.

Effektiv final — bu... yalnız bir dəfə mənimsədilən və daha sonra dəyişdirilməyən dəyişəndir. Mütləq final yazmaq lazım deyil, kompilyator özü anlayacaq.

3. Lambda ifadələri dəyişənləri necə tutur?

Lambda yazıb xarici dəyişəndən istifadə etdiyiniz zaman, Java bu dəyişəni lambda ilə birlikdə «qablaşdırır». Lambda yaradıldığı metod artıq başa çatsa belə, dəyişən itmir — closure daxilində «yaşamağa» davam edir.

İllüstrasiya: lambda dəyişəni «yada saxlayır»

public static Runnable createGreeter(String name) {
    // name — metodun parametridir, lambda tərəfindən tutulacaq
    return () -> System.out.println("Salam, " + name + "!");
}

public static void main(String[] args) {
    Runnable greeter = createGreeter("Vasya");
    greeter.run(); // Salam, Vasya!
}

Burada name dəyişəni artıq main metodunun stekində mövcud deyil, amma greeter onun dəyərini hələ də «xatırlayır».

Bu daxildə necə reallaşdırılır?

Java kompilyatoru xüsusi köməkçi obyekt yaradır (ona «capture/display class» da deyirlər) ki, bütün tutulan dəyişənləri saxlasın. Lambda ifadəsi isə bu «konteyner»ə istinad edən obyektə çevrilir.

4. Closure nümunəsi: dəyişəndən istifadə edən funksiyanı qaytarmaq

Daxil olduğu konteksdən dəyişəndən istifadə edən lambda qaytaran bir funksiyanı yazaq:

import java.util.function.IntSupplier;

public class ClosureFactory {
    public static IntSupplier makeAdder(int x) {
        // x — lambda tərəfindən tutulur
        return () -> x + 10;
    }

    public static void main(String[] args) {
        IntSupplier adder = makeAdder(5);
        System.out.println(adder.getAsInt()); // 15
    }
}

Burada x dəyişəni artıq metodun stekindən «çıxıb», amma lambda onu hələ də istifadə edə bilir.

5. Niyə tutulan dəyişənləri dəyişmək olmaz?

public static void main(String[] args) {
    int base = 100;
    Runnable printer = () -> System.out.println(base);
    base = 200; // SƏHV!
    printer.run();
}

Kompilyator buna icazə verməyəcək. Əgər base dəyişənini dəyişə bilsəydik, lambdada dəyişənin hansı versiyasından istifadə olunacağı aydın olmazdı: köhnə, yoxsa yeni? Buna görə də Java lambda tərəfindən tutulan lokal dəyişənlərin dəyişdirilməsini qadağan edir.

Lambdada nədən istifadə etmək olar?

  • İlkinləşdirmədən sonra dəyişməyən lokal dəyişənlər (effektiv final).
  • Sinif sahələri (həm static, həm də qeyri-static) — onları dəyişmək olar, amma bu artıq başqa mexanizmdir (obyektin vəziyyətinə müraciət), lokal dəyişənin tutulması deyil.

6. Anonim siniflərlə müqayisə

Lambda ifadələrindən əvvəl Java-da closure-ları anonim siniflərlə də etmək olardı:

public static void main(String[] args) {
    String word = "Java";
    Runnable r = new Runnable() {
        public void run() {
            System.out.println(word);
        }
    };
    r.run(); // Java
}

Qaydalar eynidir: word dəyişəni final və ya effektiv final olmalıdır.

Fərq: this üçün görünürlük sahəsi (scope)

  • Anonim sinifdə this anonim sinifin nümunəsinə işarə edir.
  • Lambda ifadəsində this xarici obyektə (məsələn, cari sinif nümunəsinə) işarə edir.

7. Closure və sinif sahələri

Əgər lambda sinif sahəsindən istifadə edirsə, bu, dar mənada «lokal dəyişənin tutulması» deyil — sahə həmişə əlçatandır və onu dəyişmək olar.

public class Counter {
    private int count = 0;

    public Runnable makeCounter() {
        return () -> {
            count++;
            System.out.println("Sayğac: " + count);
        };
    }

    public static void main(String[] args) {
        Counter c = new Counter();
        Runnable r = c.makeCounter();
        r.run(); // Sayğac: 1
        r.run(); // Sayğac: 2
    }
}

8. Java-da closure-ların tipik səhvləri və xüsusiyyətləri

Səhv № 1: lambda tərəfindən tutulan dəyişəni dəyişdirməyə cəhd. Ən çox rast gəlinən səhv — lambdada istifadə olunduqdan sonra lokal dəyişəni dəyişməyə cəhd etməkdir. Kompilyator belə deyəcək: Variable used in lambda expression should be final or effectively final.

Səhv № 2: dəyişənin «dondurulduğunu» gözləmək. Java-da tutulan dəyişən sinif sahəsi olduqda bu, dəyərin surəti deyil, orijinal obyektə istinaddır. Sinif sahəsi dəyişərsə, lambda yeni dəyəri görəcək. Amma lambdada lokal dəyişənlər yalnız effektiv final ola bilər.

Səhv № 3: lambdanın this üçün yeni görünürlük sahəsi yaratdığını düşünmək. Lambdada this — xarici obyektdir (konteyner sinif). Anonim sinifdə isə this — elə həmin anonim sinifin özüdür.

Səhv № 4: dəyişən (mutable) obyektlərdən istifadə. Əgər dəyişən (məsələn, siyahı) obyektə istinadı tutursunuzsa, lambda daxilində onun məzmununu dəyişə bilərsiniz, hətta dəyişən özü effektiv final qalsa belə:

public static void main(String[] args) {
    java.util.List<String> list = new java.util.ArrayList<>();
    Runnable r = () -> list.add("Hello");
    r.run();
    System.out.println(list); // [Hello]
}

Burada list dəyişəni dəyişmir (biz list = ... etmirik), amma onun daxilindəki obyekt dəyişir.

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