Problem: vərdişlə seçilmiş kolleksiya
Hesabat endpoint-i bir neçə min sifariş sətrini yükləyir və hər biri üçün məhsul id-sinin List<String> tipli "satışdan çıxarılmış məhsullar" siyahısında olub-olmadığını yoxlayır. On sətirlik testdə hər şey işləyir. Real məlumatla endpoint nəzərəçarpacaq dərəcədə yavaşlayır, çünki List.contains hər dəfə siyahını əvvəldən skan edir: n sətir vurulsun m məhsula. Siyahını HashSet<String>-ə dəyişmək hər yoxlamanı tək hash axtarışına çevirir, kod isə uzanmır.
Dərs "həmişə HashSet işlədin" deyil. Dərs odur ki, hər kolleksiya bəzi əməliyyatları ucuz, digərlərini bahalı edir və düzgün seçim məlumata necə müraciət edəcəyinizdən asılıdır. Bu yazı qərar vermək üçün bir yol təqdim edir.
Sinifdən yox, sualdan başlayın
Bu sualları ardıcıllıqla verin:
- Açar üzrə axtarış lazımdır? Onda
Map. - Yalnız elementin olub-olmadığını bilmək kifayətdir və təkrar olmamalıdır? Onda
Set. - Sıra vacibdir və mövqeyə görə müraciət lazımdır? Onda
List. - Növbə və ya stack kimi uclardan əlavə edib çıxarırsınız? Onda
Deque(ArrayDeque). - İterasiya sırası vacibdir? Daxil edilmə sırası, sıralanmış sıra və ya "fərqi yoxdur" müxtəlif implementasiyalara işarə edir.
İnterfeysə (List, Set, Map) qarşı proqramlaşdırmaq implementasiya seçimini bir sətirdə lokal saxlayır.
Map<String, Customer> byId = new HashMap<>();List<OrderLine> lines = new ArrayList<>();Deque<Task> work = new ArrayDeque<>();List: ArrayList və LinkedList
ArrayList elementləri ardıcıl massivdə saxlayır. İndeksə görə müraciət sabit vaxtdadır, sona əlavə orta hesabla ucuzdur, elementlər yaddaşda bir-birinə yaxın olduğu üçün iterasiya CPU keşi üçün əlverişlidir. Ortaya əlavə və ya oradan silmək sonrakı elementlərin sürüşdürülməsi deməkdir.
LinkedList hər elementi qonşularına işarə edən node-da saxlayır. İterator artıq həmin mövqedə olanda əlavə nəzəri olaraq ucuzdur, amma o mövqeyə çatmaq node-ları gəzmək deməkdir və hər node ayrıca obyektdir, əlavə yaddaş yükü var. Praktikada əksər yüklərdə ArrayList yaddaş lokallığına görə eyni sürətdədir və ya daha sürətlidir, hətta çox əlavə olsa belə. Real servis yükündə LinkedList nadir hallarda qalib gəlir; növbə kimi isə ArrayDeque adətən daha yaxşı alətdir.
Set və Map: üç variant
HashSet və HashMap gözlənilən sabit vaxtda əlavə, silmə və axtarış verir. İterasiya sırasına söz vermirlər və düzgün equals və hashCode müqaviləsindən asılıdırlar.
LinkedHashMap (və LinkedHashSet) hash analoqları kimi davranır, amma daxil edilmə sırasını yadda saxlayır, buna görə iterasiya proqnozlaşdırıla bilir. Access-order konstruktoru ilə removeEldestEntry-ni override edərək kiçik LRU keşi üçün də əsas ola bilər.
TreeMap (və TreeSet) açarları Comparable və ya Comparator ilə sıralı saxlayır. Əməliyyatlar loqarifmik vaxt tələb edir, amma diapazon sorğuları və naviqasiya əldə edirsiniz: firstKey, floorKey, headMap, subMap. Sıralı çıxış səliqəli göründüyü üçün yox, "X-dən sonrakı və ya X-ə bərabər ən yaxın dəyər" və ya sıralı iterasiya lazım olanda seçin.
var rates = new TreeMap<LocalDate, BigDecimal>();rates.put(LocalDate.of(2026, 1, 1), new BigDecimal("1.70"));rates.put(LocalDate.of(2026, 6, 1), new BigDecimal("1.72"));// verilmiş gündə qüvvədə olan məzənnə = həmin gündən əvvəlki ən son qeydBigDecimal rate = rates.floorEntry(LocalDate.of(2026, 3, 15)).getValue();HashMap-də eyni axtarış bütün açarları skan etməyi tələb edərdi.
Növbə və stack üçün Deque
ArrayDeque hər iki ucdan səmərəli əlavə və çıxarmanı dəstəkləyir. Stack kimi (push, pop, peek) köhnə Stack sinfi əvəzinə, növbə kimi (offer, poll) LinkedList əvəzinə işlədin. ArrayDeque null elementlərə icazə vermir və thread-safe deyil. Thread-lər arasında ötürmə üçün concurrency əsasları məqaləsində bəhs olunan java.util.concurrent növbələrinə baxın.
İmmutable kolleksiyalar
List.of, Set.of və Map.of (Java 9+) dəyişdirilə bilməyən kolleksiyalar yaradır. Onlar null elementləri qəbul etmir, Set.of və Map.of isə təkrarlar olduqda yaradılma anında istisna atır. Set və map-lər üçün iterasiya sırası qəsdən müəyyən edilməyib və işə düşmələr arasında fərqlənə bilər, ona görə ona arxalanmayın.
List<String> roles = List.of("USER", "ADMIN");roles.add("AUDITOR"); // UnsupportedOperationExceptionList<String> copy = List.copyOf(source); // müdafiə məqsədli snapshotİki detala diqqət edin. Collections.unmodifiableList(list) yalnız oxunuşlu görünüşdür: kimsə orijinal siyahını saxlayıb dəyişsə, görünüş də dəyişir. List.copyOf isə həqiqi kopya yaradır. Həm də "unmodifiable" səthidir: elementlərin özləri dəyişən obyekt ola bilər.
Metodlarınızdan immutable kolleksiya qaytarmaq çağıranın daxili vəziyyəti təsadüfən dəyişməsinin qarşısını alır və thread-lər arasında paylaşmağı təhlükəsizləşdirir.
Qərar cədvəli
| Ehtiyac | Yaxşı default | Səbəb | Diqqət |
|---|---|---|---|
| Sıralı elementlər, indeksə görə müraciət | ArrayList | Ardıcıl yaddaş, sürətli indeks | Ortaya əlavə elementləri sürüşdürür |
| Sürətli üzvlük yoxlaması | HashSet | Gözlənilən sabit vaxtlı contains | Düzgün equals/hashCode lazımdır |
| Açar üzrə axtarış | HashMap | Gözlənilən sabit vaxtlı get | İterasiya sırası yoxdur |
| Proqnozlaşdırıla bilən sıra | LinkedHashMap / LinkedHashSet | Hash sürəti və sıra | Bir az çox yaddaş |
| Sıralı açarlar, diapazon sorğuları | TreeMap / TreeSet | Naviqasiya metodları | Loqarifmik xərc, comparator equals ilə uyğun olmalıdır |
| Növbə və ya stack | ArrayDeque | Hər iki ucda sürətli | Null yoxdur, thread-safe deyil |
| Sabit, yalnız oxunan data | List.of, Map.of | İmmutable, yığcam | Null yoxdur, sıra müəyyən deyil |
| Thread-lər arasında paylaşım | ConcurrentHashMap | Təhlükəsiz paralel müraciət | Mürəkkəb əməliyyatlar yenə diqqət tələb edir |
Tez-tez edilən səhvlər
- Böyük siyahı üzərində dövrün içində
List.containsvə yalist.remove(object)işlətmək. ƏvvəlcəSetvə yaMap-ə çevirin. - "Əlavə O(1)-dir" deyə
LinkedListseçmək, ölçmədən və mövqeyi tapmağın O(n) olduğunu nəzərə almadan. - For-each ilə iterasiya edərkən siyahını dəyişmək; bu,
ConcurrentModificationExceptionatır.removeIfvə yaIteratorişlədin. - Testlərdə və çıxışda
HashMapvə yaHashSetiterasiya sırasına güvənmək. - Getter-dən daxili dəyişən kolleksiyanı açıq qaytarmaq.
List.copyOf(items)və ya unmodifiable görünüşü şüurlu qaytarın. - Adi
HashMap-i thread-lər arasında paylaşmaq. Nəticə itirilmiş yeniləmələrdən pozulmuş vəziyyətə qədər dəyişir; concurrent map işlədin və ya bir thread-ə məhdudlaşdırın. - Başlanğıc tutumu düşünmədən vermək; ölçünü bilirsinizsə,
new ArrayList<>(n)təkrar böyütməni azaldır, amma bu optimallaşdırmadır, zərurət deyil.
Güzəştlər
Hash əsaslı strukturlar sıranı sürətə dəyişir. Ağac əsaslı olanlar bir qədər sürəti sıralı müraciətə dəyişir. Linked-hash variantlar bir az yaddaşı proqnozlaşdırıla bilən çıxışa dəyişir. İmmutable kolleksiyalar çevikliyi təhlükəsizliyə və aydın düşüncəyə dəyişir. Geri qaytarması ən ucuz qərar interfeysin arxasındakı implementasiya sinfidir, buna görə çox narahat olmayın; daha bahalı səhv yanlış növ strukturdur, məsələn map yerinə siyahı skan etmək.
Unutmayın ki, asimptotik xərc hekayənin yalnız bir hissəsidir. Bir neçə onluq elementdən ibarət kiçik kolleksiyalarda implementasiyalar arasındakı fərq adətən əhəmiyyətsizdir və niyyətin aydınlığı üstün olmalıdır.
Yoxlama siyahısı
- Seçməzdən əvvəl əsas müraciət nümunəsini bir cümlə ilə yazın.
- Dəyişənləri interfeys tipi ilə elan edin.
- Üzvlük və açar axtarışı üçün
Set/Mapişlədin. - Növbə və stack üçün
Stackvə yaLinkedListyox,ArrayDequeişlədin. - Public metodlardan immutable və ya kopyalanmış kolleksiya qaytarın.
- Concurrent olmayan kolleksiyaları thread-lər arasında paylaşmayın.
Növbəti addım
groupingBy və toMap-in siyahılardan map-ləri necə qurduğunu görmək üçün stream pipeline və collector-lar məqaləsini, thread-safe kolleksiyalar üçün concurrency əsaslarını oxuyun. Kolleksiyaların digər əsas bacarıqlar arasında yerini junior Java developer yol xəritəsi göstərir.