CodeGym /Kurslar /JAVA 25 SELF /Immutability — record-siniflərin dəyişməzliyi

Immutability — record-siniflərin dəyişməzliyi

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

1. Record-siniflər və dəyişməzlik

Dəyişməzlik — bu, obyektin elə bir xüsusiyyətidir ki, onun vəziyyəti yaradıldıqdan sonra dəyişdirilə bilmir. Başqa sözlə: obyekt yaradılıbsa, siz onun daxili məlumatlarını dəyişə bilməzsiniz. Vəssalam! Daşa həkk olunmuş kimidir.

Təsəvvür edin, qatar bileti. O çap olunan kimi, artıq çıxış tarixi və yerini dəyişə bilmirsiniz (əlbəttə, “photoshop” fəndlərini kənara qoysaq). Bilet — dəyişməz obyektdir. Başqa bilet istəyirsinizsə — yenisini alırsınız.

Proqramlaşdırmada belə obyektlərə immutable objects deyilir. Onlar proqramı təsadüfi dəyişikliklərdən qoruyur, kodu daha təhlükəsiz və proqnozlaşdırıla bilən edir.

Dəyişməz obyektin əlamətləri

  • Bütün sahələr — final-dır (onlara yalnız bir dəfə, adətən konstruktor-da dəyər verilə bilər).
  • Setter-lər yoxdur (sahələrin dəyərlərini dəyişən metodlar yoxdur).
  • Daxili məlumatları qaytaran bütün metodlar ya nüsxələr qaytarır, ya da qaytarılan məlumatların özləri də dəyişməz olur.

Java-da record-siniflər məhz dəyişməz məlumat strukturlarını sadə və ağrısız yaratmaq üçün nəzərdə tutulub.

Niyə record dəyişməzdir?

  • Record-un bütün komponentləri final-dır.
    record elan edəndə kompilyator onun bütün sahələrini avtomatik olaraq private final edir. Bu o deməkdir ki, obyekt yaradıldıqdan sonra onun sahələrini dəyişə bilməyəcəksiniz.
  • Setter-lər yoxdur.
    Record-sinifdə setX(int x) metodunu əlavə etmək olmaz — kompilyator sahənin dəyərinin obyekt yaradıldıqdan sonra dəyişdirilməsinə icazə vermir.
  • Konstruktor dəyərləri yalnız bir dəfə təyin edir.
    Bütün dəyərlər obyektin yaradılması anında verilir.

Nümunə

public record Point(int x, int y) {}

Point p = new Point(5, 10);
// p.x = 7; // Kompilyasiya xətası: x sahəsi private girişlidir və final-dır
// p.x(7);  // Xəta: setter yoxdur!
System.out.println(p.x()); // 5

Sahəni dəyişmək və ya mövcud olmayan setter çağırmaq cəhdi kompilyasiya xətasına gətirir. Java dəyişməzlik müqaviləsinə ciddi nəzarət edir.

2. Dəyişməz obyektlərin üstünlükləri

Multithreading-də təhlükəsizlik

Çoxaxınlı proqramlarda (indi onların çoxu belədir!) dəyişməz obyektlər — gülləkeçirməz jilet kimidir. Obyekt dəyişdirilə bilmirsə, müxtəlif axınlar onu rahatlıqla oxuya bilər, kimsə eyni anda məlumatları dəyişəcək deyə narahat olmaq lazım deyil. Girişi sinxronlaşdırmağa və data race-lərdən (race condition) qorxmağa ehtiyac yoxdur.

Fakt: standart Java kitabxanasındakı, çoxaxınlı ssenarilərdə fəal istifadə olunan siniflərin bir qismi ya dəyişməzdir, ya da dəyişikliklərə qarşı xüsusi qorunur.

Kodu anlamağı sadələşdirir

Obyekt dəyişməzdirsə, siz həmişə əminsiniz: onu başqa metoda və ya sinfə ötürdünüz — və o, “sizin arxanızca” dəyişməyəcək. Bu, kodun oxunmasını və sazlanmasını xeyli asanlaşdırır. Kim və harada sahəni dəyişmiş ola bilər — heç kim!

Kolleksiyalarda açar kimi istifadə etmək rahatdır

Dəyişməz obyektlər HashMap və ya HashSet kimi kolleksiyalarda açar kimi istifadə üçün əladır. Niyə? Çünki onların equalshashCode metodları yalnız dəyişməyən sahələrdən asılıdır. Deməli, obyekt vəziyyəti dəyişdiyi üçün kolleksiyada “itirilməyəcək”.

Gizli xətaların sayı azalır

Dəyişən obyektə təsadüfən harasa istinad ötürməklə asanlıqla zərər verə bilərsiniz. Dəyişməz obyekt isə çap olunmuş kitab kimidir: heç kim səhifəni cırıb ya da yenidən yaza bilməz.

3. Adi siniflərlə müqayisə

Gəlin adi sinifin və record-sinfin davranışını müqayisə edək. Məsələn, müstəvidə sadə bir nöqtə modeli götürək.

Adi sinif (mutable)

public class PointClass {
    private int x;
    private int y;

    public PointClass(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { 
        return x; 
    }
    public int getY() { 
        return y; 
    }

    public void setX(int x) { 
        this.x = x; 
    }
    public void setY(int y) { 
        this.y = y; 
    }
}

Obyekt yarada və sonra onun vəziyyətini istədiyiniz qədər dəyişə bilərsiniz:

PointClass p = new PointClass(1, 2);
p.setX(10); // p.x artıq 10

Record-sinif (immutable)

public record Point(int x, int y) {}

Obyekti yaradırsınız — vəssalam, o, necə yaradılıbsa, elə də qalır:

Point p = new Point(1, 2);
// p.x = 10;   // Xəta! Sahəyə giriş yoxdur
// p.x(10);    // Xəta! Setter yoxdur

Cədvəl: davranışın müqayisəsi

Adi sinif Record-sinif
Sahələr istənilən yalnız private final
Setter-lər əlavə etmək olar əlavə etmək olmaz
Dəyişənlik dəyişən (mutable) dəyişməz
Avtogenerasiya yoxdur bəli (equals, hashCode, toString)

4. Təcrübə: record-siniflərin dəyişməzliyindən necə istifadə etməli

Gəlin kiçik bir nümunədə record-sinifdən istifadə edək. Bank tətbiqimiz var və tranzaksiya haqqında məlumat saxlamaq istəyirik:

public record Transaction(String fromAccount, String toAccount, double amount) {}

Obyekt yaradaq:

Transaction t = new Transaction("12345", "67890", 1500.0);
System.out.println(t);
// Transaction[fromAccount=12345, toAccount=67890, amount=1500.0]

Pulları başqa hesaba “köçürməyə” cəhd edək:

// t.toAccount = "11111"; // Xəta! Sahə final-dır, giriş yoxdur
// t.toAccount("11111");  // Xəta! Setter yoxdur

Bizə başqa tranzaksiya lazımdırsa — yeni obyekt yaradırıq:

Transaction t2 = new Transaction(t.fromAccount(), "11111", t.amount());

Vacibdir: dəyişməzlik “əlverişsiz” demək deyil. Bu, sadəcə başqa bir üslubdur: yeni vəziyyət lazımdırsa — yeni obyekt yaradırsınız.

5. Dəyişməzliyin xüsusiyyətləri: nələri yadda saxlamaq lazımdır

Dəyişməzlik — hər zaman mütləq deyil!

Record-sinif onun sahələrinin dəyişməyəcəyini təmin edir. Amma sahə dəyişən obyektə (məsələn, massivə və ya adi sinfə) istinadda bulunursa, həmin obyektin məzmunu dəyişdirilə bilər.

Massiv nümunəsi

public record DataHolder(int[] data) {}

int[] arr = {1, 2, 3};
DataHolder holder = new DataHolder(arr);
arr[0] = 99;
System.out.println(holder.data()[0]); // 99! Massiv dəyişdi

Nəticə: həqiqi dəyişməzlik istəyirsinizsə, yalnız dəyişməz tiplərdən (String, Integer, başqa record-lar və s.) istifadə edin və ya record-sinifin kanonik konstruktorunda dəyişən obyektlərin müdafiəvi nüsxələrini yaradın. Məsələn:

int[] copy = Arrays.copyOf(data, data.length);

6. Adi sinfi necə özünüz dəyişməz edə bilərsiniz

Adi sinfi immutable (dəyişməz) etmək istəyirsinizsə, əl ilə aşağıdakıları etməlisiniz:

  • Bütün sahələri private final kimi işarələyin,
  • Setter-lər əlavə etməyin,
  • Bütün sahələri yalnız konstruktor vasitəsilə init edin,
  • Əgər sahə dəyişən obyektə istinaddırsa, müdafiəvi nüsxə (defensive copy) yaradın.

Nümunə

public final class User {
    private final String name;
    private final int age;

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

    public String name() { 
        return name; 
    }
    public int age() { 
        return age; 
    }
}

Razılaşın ki, record-siniflə bunu etmək daha sadə və qısadır:

public record User(String name, int age) {}

7. Dəyişməz record-siniflərlə işləyərkən tipik səhvlər

Səhv №1: obyekt yaradıldıqdan sonra sahəni dəyişməyə cəhd.
Yeni başlayanlar tez-tez record-obyekt üçün p.x = 42; və ya p.x(42); yazmağa çalışırlar. Amma kompilyator dərhal deyəcək: “Belə olmaz! Sahə final-dır, setter yoxdur”.

Səhv №2: record komponentləri kimi dəyişən obyektlərdən istifadə.
Əgər record-a List, Map, massiv və ya başqa dəyişən obyekt tipində sahə əlavə etmisinizsə, record həmin obyektin məzmununun dəyişdirilməsindən sizi qorumayacaq. Məsələn, əgər sizdə record User(List<String> hobbies) varsa, kimsə siyahıya element əlavə edib ya da silə bilər və bu, record-obyektinizin vəziyyətini dəyişər. Bunun qarşısını almaq üçün dəyişməz kolleksiyalardan (List.copyOf, Collections.unmodifiableList) istifadə edin və ya kolleksiyaların nüsxələrini record-un konstruktorunda yaradın.

Səhv №3: dəyişməzliyin düzgün başa düşülməməsi.
Bəziləri düşünür ki, əgər obyekt record-dursa, o, istənilən dəyişiklikdən qorunur. Əslində isə sahələr dəyişən obyektlərə istinad edirsə, onların məzmunu dəyişdirilə bilər və bu, gözlənilməz xətalara səbəb olar.

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