CodeGym /Kurslar /JAVA 25 SELF /CopyOnWrite kolleksiyaları, dəyişdirilə bilməyən örtüklər...

CopyOnWrite kolleksiyaları, dəyişdirilə bilməyən örtüklər

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

1. Unmodifiable wrappers: kolleksiyalar üzərində örtüklər

Bəzən kodda artıq elə bir kolleksiya olur ki, kimsə onu təsadüfən (və ya elə də təsadüfən olmayaraq) dəyişə bilər. Məsələn, sizdə kənara ötürmək istədiyiniz, amma kimsənin dəyişməsini istəmədiyiniz bir istifadəçi siyahısı var:

List<String> users = new ArrayList<>();
users.add("Alice");
users.add("Bob");

Siz bu siyahını metoddən qaytarırsınız, kimsə isə users.add("Hacker") edir — və budur, sisteminizdə icazəsiz yeni istifadəçi! Necə qorunmalı?

Collections-dən örtüklər

Java-da çoxdan bu məqsəd üçün Collections sinfində xüsusi örtük metodları var:

  • Collections.unmodifiableList(list)
  • Collections.unmodifiableSet(set)
  • Collections.unmodifiableMap(map)

Bu metodlar kolleksiyanızın üzərində onu özündən dəyişməyə qoymayan bir örtük qaytarır. Örtük vasitəsilə element əlavə etmək, silmək və ya dəyişdirmək cəhdi UnsupportedOperationException atacaq.

Nümunə:

import java.util.*;

public class Demo {
    public static void main(String[] args) {
        List<String> modifiable = new ArrayList<>();
        modifiable.add("Alice");
        modifiable.add("Bob");

        // Dəyişdirilə bilməyən örtük yaradırıq
        List<String> unmodifiable = Collections.unmodifiableList(modifiable);

        System.out.println(unmodifiable); // [Alice, Bob]

        // Örtük vasitəsilə element əlavə etməyə cəhd edirik
        try {
            unmodifiable.add("Charlie"); // Bum! UnsupportedOperationException
        } catch (UnsupportedOperationException e) {
            System.out.println("Kolleksiyanı dəyişmək olmaz: " + e);
        }
    }
}

Vacibdir!

  • Örtük orijinal kolleksiyanı DƏYİŞMƏZ etmir. Kimsə orijinal kolleksiyaya istinad saxlayırsa, onu hələ də dəyişə bilər.
  • Orijinal kolleksiyadakı bütün dəyişikliklər örtükdən görünür.
modifiable.add("Charlie");
System.out.println(unmodifiable); // [Alice, Bob, Charlie]

Yəni, kodun haradasa bir yerində kimsə orijinal siyahıya element əlavə/silsə, örtük bunu görəcək. Bu “dondurma” deyil, sadəcə örtüyün özündən dəyişdirməyə qadağadır.

Digər kolleksiyalar üçün örtüklər

Eyni şəkildə Set, Map və hətta daha ekzotik strukturlar üçün də örtüklər yarada bilərsiniz:

Set<Integer> numbers = new HashSet<>(Set.of(1, 2, 3));
Set<Integer> unmodSet = Collections.unmodifiableSet(numbers);

Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
Map<String, Integer> unmodMap = Collections.unmodifiableMap(ages);

Fabrika metodları ilə müqayisə (List.of və s.)

  • List.of(...) yeni dəyişməz kolleksiya yaradır; ora heç vaxt element əlavə etmək mümkün deyil.
  • Collections.unmodifiableList(list) — bu, mövcud kolleksiya üzərində örtükdür. Əgər orijinal siyahı dəyişərsə, örtük də dəyişər.

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

List.of(...)
Collections.unmodifiableList(list)
Əlavə etmək olar? Yox Yox (örtük vasitəsilə)
Orijinala əlavə etmək olar? Tətbiq edilmir Bəli
Dəyişikliklər görünür? Yox Bəli
null əlavə etmək olar? Yox (NPE) Bəli (əgər orijinal kolleksiya buna icazə verirsə)
İmplementasiya Öz implementasiya Sizin kolleksiyanız üzərində örtük

2. CopyOnWrite kolleksiyaları

Çox axınlı proqramlarda tez-tez belə bir vəzifə ortaya çıxır: bir axın (və ya bir neçəsi) kolleksiyanı oxuyur, digər(ləri) isə bəzən onu dəyişir. Adi kolleksiyalar burada uyğun deyil: yarış vəziyyətləri, səhvlər, ConcurrentModificationException və çoxaxınlı dünyanın digər “məzələri”.

Belə hallar üçün CopyOnWrite kolleksiyaları icad edilib — onlar oxumaların tez-tez, dəyişikliklərin isə nadir baş verdiyi ssenarilər üçün xüsusi yaradılıb.

Bu necə işləyir?

  • Hər dəyişiklikdə (əlavə, silmə, əvəzləmə) kolleksiya daxili massivinin yeni surətini yaradır.
  • Oxuyan bütün axınlar massivin onlara məxsus bir versiyasını alırlar; bu versiya oxuduqları müddətdə dəyişmir.
  • Bu, oxunu tam təhlükəsiz edir və sinxronizasiya tələb etmir.

Əsas siniflər

  • CopyOnWriteArrayList<E>
  • CopyOnWriteArraySet<E>

Onlar java.util.concurrent paketində yerləşir.

İstifadə nümunəsi

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
        cowList.add("Alpha");
        cowList.add("Beta");

        // Kimsə paralel olaraq elementlər əlavə etsə belə, təhlükəsiz şəkildə iterasiya etmək olar
        for (String s : cowList) {
            System.out.println(s);
            cowList.add("Gamma"); // ConcurrentModificationException yaratmayacaq!
        }

        System.out.println(cowList); // [Alpha, Beta, Gamma, Gamma]
    }
}

Xüsusiyyətlər:

  • CopyOnWrite kolleksiyalarının iteratoru həmişə yaradıldığı andakı “şəklini” (snapshot) görür.
  • İterator yaradıldıqdan sonra kimsə elementlər əlavə etsə, iterator onları görməyəcək.
  • Keçid zamanı elementləri təhlükəsiz şəkildə əlavə/silmək olar — heç bir ConcurrentModificationException olmayacaq!

CopyOnWrite kolleksiyalarını nə vaxt istifadə etməli?

Onlar proqramda çoxlu axınlar işləyib əsasən kolleksiyadan oxuyanda, dəyişiklik əməliyyatları isə çox nadir olduqda uyğundur. Klassik nümunə — hadisə dinləyiciləri (event listeners) siyahısı: yeni dinləyicilər nadir hallarda əlavə/silinir, lakin bu dinləyicilərin bildirişləndirilməsi daima icra olunur.

Nümunə — hadisə abunəçiləri

import java.util.concurrent.CopyOnWriteArrayList;

public class EventBus {
    private final CopyOnWriteArrayList<Runnable> listeners = new CopyOnWriteArrayList<>();

    public void subscribe(Runnable listener) {
        listeners.add(listener);
    }

    public void publishEvent() {
        for (Runnable listener : listeners) {
            listener.run(); // təhlükəsizdir, kimsə elə indi abunə olub/çıxsa belə!
        }
    }
}

CopyOnWrite kolleksiyalarının mənfi cəhətləri

  • Tez-tez dəyişikliklər üçün yavaşdır: hər dəyişiklik — daxili massivinin yeni surətinin yaradılmasıdır; bu, yaddaş və vaxt baxımından bahadır.
  • Böyük kolleksiyalar üçün qeyri-effektivdir: kolleksiya böyükdürsə, massivi kopyalamaq baha başa gəlir.

3. Müqayisə: nəyi nə vaxt istifadə etməli?

Unmodifiable wrappers (Collections.unmodifiable...)

Nə zaman istifadə etməli: Artıq mövcud kolleksiyanız var və onu xarici kod vasitəsilə dəyişikliklərdən qorumaq istəyirsiniz, lakin daxildən (kolleksiyanın sahibi tərəfindən) dəyişikliklər məqbuldur.

Axın təhlükəsizliyi: Zəmanət verilmir! Orijinal kolleksiya başqa axından dəyişdirilsə, yarış vəziyyətləri və səhvlər yarana bilər.

Fabrika metodları (List.of, Set.of, Map.of)

Nə zaman istifadə etməli: Dərhal dəyişməz, sabit bir kolleksiya yaratmaq istəyirsiniz və heç yerdən onu dəyişmək mümkün olmasın.

Axın təhlükəsizliyi: Zəmanətlidir (kolleksiya ümumiyyətlə dəyişmir).

CopyOnWrite kolleksiyaları

Nə zaman istifadə etməli: Çox axınlı ssenarilərdə oxuma çox, dəyişikliklər isə az olduqda. Məsələn, abunəçi siyahıları üçün.

Axın təhlükəsizliyi: Bəli, tam axın təhlükəsizdir.

Dəyişməzlik: Xeyr, kolleksiyanı dəyişmək olar, lakin oxuyanlar zərər görməsin deyə hər dəfə yeni surət yaradılır.

4. Tipik səhvlər və implementasiya xüsusiyyətləri

Xəta № 1: Örtük vasitəsilə orijinal kolleksiyanın “dondurulmasını” gözləmək. Bir çoxları Collections.unmodifiableList(list) metodunun kolleksiyanı tam dəyişməz etdiyini düşünür. Əslində, kimsə orijinal siyahıya istinad saxlayırsa, onu dəyişə bilər və bu dəyişikliklər örtükdən görünəcək. Həll: Əgər həqiqi dəyişməzlik lazımdırsa — List.copyOf(list) (Java 10+) və ya List.of(...) istifadə edin.

Xəta № 2: Tez-tez dəyişən kolleksiya üçün CopyOnWrite istifadəsi. Əgər CopyOnWriteArrayList-ə daima elementlər əlavə/silinsə, bu, performans və yaddaş problemlərinə gətirib çıxaracaq. CopyOnWrite yalnız “çox oxuyan, az yazan” ssenarilər üçün uyğundur.

Xəta № 3: Örtüklərin guya axın təhlükəsiz olduğunu düşünmək. Collections.unmodifiableList kolleksiyanı axın təhlükəsiz etmir! Orijinal siyahı müxtəlif axınlardan dəyişdirilərsə, səhvlər yarana bilər.

Xəta № 4: List.of və ya Set.of-dan olan kolleksiyalarda null istifadəsi. Adi kolleksiyalardan fərqli olaraq, fabrika metodları null-a icazə vermir — null ilə kolleksiya yaratmaq və ya element əlavə etməyə cəhd NullPointerException ilə nəticələnəcək.

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