CodeGym /Kurslar /JAVA 25 SELF /Elementlərin təhlükəsiz silinməsi

Elementlərin təhlükəsiz silinməsi

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

1. ConcurrentModificationException problemi

Bu mühazirədə (demək olar ki) yeni heç nə olmayacaq. Amma o çox vacibdir, çünki səliqəsiz silmə ən bərpaolunmaz səhvlərdən biridir — xüsusən prodakşnda. Və bir maraqlı sinif də var ki, yəqin ki, onu bəyənəcəksiniz!

Belə ki, bəli, bir daha və bir daha: silmək üçün for-each qətiyyən yox! Onu nə qədər sevsəniz də.

Gəlin bir çox yeni başlayanlarda (və təkcə onlarda deyil) ağrı və əzab yaradan klassik nümunədən başlayaq:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));

// Bütün cüt ədədləri silməyə cəhd edək
for (Integer n : numbers) {
    if (n % 2 == 0) {
        numbers.remove(n); // BAM! ConcurrentModificationException
    }
}

Belə görünür ki, hər şey işləməlidir, amma əslində proqram istisna atır:

Exception in thread "main" java.util.ConcurrentModificationException

İndi isə gəlin burada nə baş verdiyini daha ətraflı anlayaq. Siz for-each (və ya adi Iterator) vasitəsilə kolleksiyanı keçəndə, kolleksiya daxilində xüsusi “dəyişiklik sayğacı” saxlanılır. Əgər keçid zamanı kolleksiya elə həmin iterasiyaedici vasitəsilə dəyişdirilmirsə, bu sayğac “kənar müdaxiləni” aşkar edir və istisna atır. Bu, səhvlərdən qorunmaq üçündür ki, proqram zədələnmiş verilənlər strukturu ilə işləməsin.

2. Iterator-dan istifadə

Keçid zamanı elementləri düzgün necə silmək olar?

Xatırladım: Iterator — kolleksiyanı keçməyə və elementləri “uçuşda” təhlükəsiz silməyə imkan verən xüsusi obyektdir. O, elə bir ofisiantdır ki, təkcə yeməkləri paylamır, həm də stolun ətrafında gəzərkən boşqabı yerindən götürə bilir.

İteratorun alınması

Iterator<Integer> it = numbers.iterator();

while ilə keçid və it.remove() vasitəsilə silmə

Budur, siyahıdakı bütün cüt ədədləri silməyin düzgün yolu:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));

Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
    Integer n = it.next();
    if (n % 2 == 0) {
        it.remove(); // Cari elementin təhlükəsiz silinməsi
    }
}
System.out.println(numbers); // [1, 3, 5]

Vacib məqam: elementləri yalnız iterasiyaedici vasitəsilə (it.remove()) və yalnız it.next() çağırıldıqdan sonra silmək olar. next() olmadan ard-arda iki dəfə remove() çağırsanız, IllegalStateException alacaqsınız.

3. ListIterator: genişləndirilmiş imkanlar

Budur, mühazirənin əvvəlində anons etdiyimiz yenilik! ListIterator — siyahılar (List) üçün “gücləndirilmiş” iterator olub, təkcə silməyə deyil, həm də keçid zamanı elementlər əlavə etməyə, həmçinin hər iki istiqamətdə (irəli və geri) hərəkət etməyə imkan verir.

Adi Iterator-dan fərqi

  • Iterator — sadə və təkxətli: yalnız irəli, yalnız silmək.
  • ListIterator — çevik və hərəkətli: irəli və geri, silmək, add() vasitəsilə əlavə etmək və üstəlik, cari elementi set() metodu ilə əvəz etmək olar.

Nümunə: elementlərin silinməsi və əlavə edilməsi

List<String> words = new ArrayList<>(List.of("cat", "dog", "bird"));

ListIterator<String> it = words.listIterator();
while (it.hasNext()) {
    String word = it.next();
    if (word.length() == 3) {
        it.remove(); // 3 hərfli sözləri silirik
        it.add("pet"); // Silinən sözdən sonra dərhal "pet" əlavə edirik
    }
}
System.out.println(words); // [pet, pet, bird]

Qeyd: it.add() vasitəsilə əlavə etmə, elementi iteratorun cari mövqeyindən dərhal sonra daxil edir.

4. removeIf ilə silmə

Java 8-dən etibarən, yığcam və rahat removeIf metodu peyda oldu. O, lambda ifadəsini (və ya istənilən Predicate) qəbul edir və şərt true qaytardıqda bütün elementləri silir.

Nümunə: bütün cüt ədədlərin silinməsi

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));

numbers.removeIf(n -> n % 2 == 0);

System.out.println(numbers); // [1, 3, 5]

Bu, təkcə daha qısa deyil, həm də təhlükəsizdir: daxilində düzgün iterator istifadə olunur — heç bir ConcurrentModificationException olmayacaq.

Nümunə: 3 simvoldan qısa sətirlərin silinməsi

List<String> words = new ArrayList<>(List.of("hi", "cat", "no", "elephant"));

words.removeIf(word -> word.length() < 3);

System.out.println(words); // [cat, elephant]

Məsləhət: əgər sadəcə şərtə görə elementləri silmək lazımdırsa — removeIf istifadə edin. Bu, ən yığcam və müasir üsuldur.

5. Praktik tövsiyələr

Hansı üsul daha uyğundur?

  • Əgər mürəkkəb şərtə görə elementləri silmək lazımdır və Java 8+ istifadə edirsinizsə: removeIf istifadə edin — qısa, anlaşılan, təhlükəsiz.
  • Əgər köhnə Java versiyasındasınız və ya daha mürəkkəb keçid məntiqi lazımdırsa: Iterator və onun remove() metodundan istifadə edin.
  • Əgər List ilə işləyirsinizsə və keçid zamanı təkcə silmək deyil, həm də əlavə etmək istəyirsinizsə: ListIterator istifadə edin.

Müxtəlif kolleksiya tipləri üçün xüsusiyyətlər

  • List: göstərilən bütün yanaşmaları dəstəkləyir (Iterator, ListIterator, removeIf).
  • Set: indekslər yoxdur, amma standart Iterator və removeIf işləyir.
  • Map: şərtə görə silmək üçün entrySet() üzrə iterator istifadə edin:
    Map<String, Integer> map = new HashMap<>(Map.of("a", 1, "b", 2, "c", 3));
    Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<String, Integer> entry = it.next();
        if (entry.getValue() % 2 == 0) {
            it.remove();
        }
    }
    System.out.println(map); // {a=1, c=3}
    
    Java 8+ ilə hər şey qat-qat sadələşir:
    map.entrySet().removeIf(entry -> entry.getValue() % 2 == 0);
    

6. Təcrübədən nümunə: istifadəçilərin filtrasiyası

Tutaq ki, bizdə istifadəçilərin siyahısı var və 18 yaşdan kiçik bütün istifadəçiləri silmək istəyirik.

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

List<User> users = new ArrayList<>(List.of(
    new User("Anya", 17),
    new User("Boris", 20),
    new User("Vika", 15),
    new User("Gleb", 25)
));

// removeIf vasitəsilə yetkinlik yaşına çatmayanları silirik
users.removeIf(user -> user.age < 18);

System.out.println(users); // [Boris (20), Gleb (25)]

7. Yanaşmaların müqayisəsi

Möhkəmləndirmək üçün kiçik bir cədvəl tərtib edək. Beynimiz belə şeyləri sevir.

Yanaşma Hansı versiyadan etibarən Qısalıq Təhlükəsizlik Çeviklik
for-each + remove()
Java 5+ - ❌ -
Iterator + remove()
Java 5+ + ✅ +
ListIterator
Java 5+ + ✅ ++
removeIf
Java 8+ ++ ✅ +

8. Kolleksiyalardan element silərkən tipik səhvlər

Səhv №1: for-each daxilində elementləri silməyə cəhd

for (String s : list) {
    if (s.equals("test")) {
        list.remove(s); 
    }
}

Artıq bilirsiniz: belə etmək olmaz — ConcurrentModificationException alacaqsınız! İterator və ya removeIf istifadə edin.

Səhv №2: next() olmadan iteratorda remove() çağırmaq

Iterator<String> it = list.iterator();
it.remove(); // IllegalStateException — next() çağırılmadan silmək olmaz

Səhv №3: dəyişdirilə bilməyən kolleksiyadan element silməyə cəhd

List<String> immutable = List.of("a", "b", "c");
immutable.removeIf(s -> s.equals("a")); // UnsupportedOperationException

Silinmə metodları dəyişdirilə bilməyən kolleksiyalar üçün dəstəklənmir.

Səhv №4: Map-dən elementləri values() və ya keySet() vasitəsilə iteratordan istifadə etmədən silməyə cəhd

for (String key : map.keySet()) {
    if (key.startsWith("a")) {
        map.remove(key); // ConcurrentModificationException!
    }
}

entrySet() üzrə iterator və ya removeIf istifadə edin.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Evdə saxlanılan heyvanların "Dost" heyvan mağazısında yenidən ixtisaslaşdırılması 🐶
Evdə saxlanılan heyvanların "Dost" heyvan mağazısında yenidən ixtisaslaşdırılması 🐶
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Müsabiqə iştirakçılarının siyahısının "şübhəli" nəticələrdən təmizlənməsi 🏅
Müsabiqə iştirakçılarının siyahısının "şübhəli" nəticələrdən təmizlənməsi 🏅
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION