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:
Java 8+ ilə hər şey qat-qat sadələşir: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}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 |
|---|---|---|---|---|
|
Java 5+ | - | ❌ | - |
|
Java 5+ | + | ✅ | + |
|
Java 5+ | + | ✅ | ++ |
|
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.
GO TO FULL VERSION