Java'nın hazır veri yapıları: Collections
ArrayList, LinkedList, ArrayDeque, Stack, Queue/Deque arayüzleri, Iterator ve HashMap. Elle yazdığın yapıların Java'daki karşılıkları ve hangi işte hangisini seçeceğin. Sınavda “what is printed” ve yapı seçimi soruları gelir.
Sekiz haftadır bağlı listeyi, yığını ve kuyruğu elle yazdık. Gerçek projede bunları sıfırdan yazmazsın: Java’nın java.util paketinde hepsinin test edilmiş, hızlı versiyonları var. Bu haftanın amacı iki şey: elle yazdığın her yapının Java’daki karşılığını tanımak ve doğru işe doğru sınıfı seçmek. Sınav soruları genellikle “what is printed?” tipinde ve küçük tuzaklar içerir: remove(1) indeks mi değer mi, Stack ile ArrayDeque’nin toString farkı, boş kuyrukta poll ile remove farkı.
Altın kurallar:
- Değişkeni arayüz tipiyle tanımla.
List<Integer> a = new ArrayList<>(); - Yığın ve kuyruk için
ArrayDeque.Stacksınıfı eskidir. - İndeksle erişim çoksa ArrayList, uçlardan ekleme çoksa ArrayDeque/LinkedList.
- Gezerken silme: Iterator.remove() ya da removeIf. for-each içinde
list.removeyapma. - Anahtarla arama HashMap.
Collections Framework’ün haritası
| Arayüz (interface) | Ne sağlar | Sınıflar (class) |
|---|---|---|
List<E> | sıralı, indeksli, tekrar olabilir | ArrayList, LinkedList |
Queue<E> | FIFO: offer, poll, peek | ArrayDeque, LinkedList, PriorityQueue |
Deque<E> | iki uçlu: addFirst, pollLast, push, pop | ArrayDeque, LinkedList |
Map<K,V> | anahtar → değer | HashMap, TreeMap, LinkedHashMap |
Set<E> | tekrar yok | HashSet, TreeSet |
Hepsi generic’tir: <Integer> gibi tip parametresi alır. İlkel tip (int, double) koyamazsın; sarmalayıcı sınıf (wrapper) kullanılır. Java int ile Integer arasında otomatik dönüşüm yapar (autoboxing).
ArrayList: büyüyen dizi
İçeride bir dizi tutar, dolunca yaklaşık 1.5 katı büyüklüğe kopyalar. 5. haftadaki “büyütülen dizi yığını” fikrinin aynısı.
List<String> s = new ArrayList<>();
s.add("A"); s.add("B"); s.add("C");
s.add(1, "X"); // araya ekle, sağı kaydır
s.set(0, "Y"); // değiştir
System.out.println(s + " size=" + s.size() + " indexOf(B)=" + s.indexOf("B") + " contains(A)=" + s.contains("A"));
[Y, X, B, C] size=4 indexOf(B)=2 contains(A)=false
TUZAK ·
remove(int)veremove(Object).List<Integer>üzerindelist.remove(1)indeks 1’i siler. Değeri 1 olan elemanı silmek içinlist.remove(Integer.valueOf(1))yaz. Java, int argüman görünce indeks versiyonunu seçer.
List<Integer> list = new ArrayList<>(List.of(10, 20, 30, 40));
list.remove(1); // indeks 1 → 20 gider
System.out.println(list);
list.remove(Integer.valueOf(30)); // değer 30
System.out.println(list);
[10, 30, 40]
[10, 40]
LinkedList: çift yönlü bağlı liste
- haftadaki DoublyLinkedList’in hazır hali. Hem
ListhemDequearayüzünü uygular, yani hem indeksle hem uçlardan kullanılabilir.
LinkedList<Integer> ll = new LinkedList<>();
ll.add(5); ll.addFirst(2); ll.addLast(8); ll.add(1, 4);
System.out.println(ll + " getFirst=" + ll.getFirst() + " get(2)=" + ll.get(2));
ll.removeLast(); ll.removeFirst();
System.out.println(ll);
[2, 4, 5, 8] getFirst=2 get(2)=5
[4, 5]
get(2) çalışır ama içeride baştan (ya da sondan, hangisi yakınsa) yürür: O(n). LinkedList üzerinde for (int i = 0; i < ll.size(); i++) ll.get(i) döngüsü toplam O(n²) olur. Gezmek için for-each kullan.
Ölçüm: n elemanı başa eklemek.
n=10000 ArrayList add(0) 3.1 ms, LinkedList addFirst 0.6 ms
n=20000 ArrayList add(0) 11.3 ms, LinkedList addFirst 0.8 ms
n=40000 ArrayList add(0) 46.2 ms, LinkedList addFirst 0.8 ms
n dört katına çıkınca ArrayList yaklaşık 15 kat yavaşladı (her ekleme O(n), toplam O(n²)). LinkedList neredeyse sabit kaldı (her ekleme O(1)). Süreler makineye göre değişir, oran değişmez.
Yığın ve kuyruk: ArrayDeque
ArrayDeque, 8. haftadaki dairesel dizi deque’sinin hazır halidir. Yığın olarak da kuyruk olarak da en hızlı seçenektir.
Deque<Integer> stack = new ArrayDeque<>(); // yığın
stack.push(1); stack.push(2); stack.push(3);
System.out.println(stack + " peek=" + stack.peek());
Queue<Integer> q = new LinkedList<>(); // kuyruk
q.offer(7); q.offer(3); q.offer(5);
System.out.println(q.poll() + " " + q.peek() + " " + q);
[3, 2, 1] peek=3
7 3 [3, 5]
Eski Stack sınıfı
java.util.Stack hâlâ var ve bazı hocalar onu kullanır. Vector’den türer, her metodu senkronizedir (yavaş) ve indeksle erişime de izin verir (yığın kuralını bozar).
Stack<Integer> st = new Stack<>();
st.push(1); st.push(2); st.push(3);
System.out.println(st.pop() + " " + st.peek() + " " + st);
3 2 [1, 2]
TUZAK · İki farklı toString. Aynı push sırasıyla
Stackdipten tepeye ([1, 2, 3]),ArrayDequetepeden dibe ([3, 2, 1]) yazar. pop ikisinde de 3 verir. Çıktı sorusunda hangi sınıf olduğuna bak.
İki aile: istisna mı, özel değer mi?
| İşlem | İstisna fırlatır | null / false döner |
|---|---|---|
| Ekle | add(e) | offer(e) |
| Çıkar | remove() | poll() |
| Bak | element() | peek() |
Boş kuyrukta:
null null
remove: NoSuchElementException
element: NoSuchElementException
deque peek=null
deque pop: NoSuchElementException
Stack pop: EmptyStackException
ArrayDeque null: NullPointerException
Son satır: ArrayDeque içine null konamaz, çünkü poll’un null döndürmesi “boş” anlamına gelir.
Iterator
Her koleksiyon bir Iterator verir: hasNext(), next(), remove(). for-each döngüsü arka planda bunu kullanır. Bağlı listede p = p.next ile yaptığın gezinmenin soyut hali.
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = nums.iterator();
while (it.hasNext())
if (it.next() % 2 == 0) it.remove();
System.out.println(nums);
[1, 3, 5]
for-each içinde listeyi doğrudan değiştirirsen iterator bunu fark eder ve bir sonraki next()’te ConcurrentModificationException fırlatır. Tek satırlık modern alternatif: nums.removeIf(x -> x % 2 == 0);
HashMap temelleri
Anahtar (key) ile değer (value) eşleyen tablo. Arama, ekleme, silme ortalama O(1). Nasıl çalıştığı (hashing) SEN2212’nin konusu; burada kullanmayı öğreniyoruz.
Map<String, Integer> count = new HashMap<>();
for (String w : "to be or not to be".split(" "))
count.put(w, count.getOrDefault(w, 0) + 1);
System.out.println(count.get("to") + " " + count.get("or") + " " + count.get("is") + " size=" + count.size());
System.out.println(new TreeMap<>(count));
2 1 null size=4
{be=2, not=1, or=1, to=2}
| Metot | Ne yapar |
|---|---|
put(k, v) | ekler ya da günceller; eski değeri döndürür (yoksa null) |
get(k) | değer ya da null |
getOrDefault(k, d) | değer ya da d |
containsKey(k) | var mı |
remove(k) | siler |
keySet(), values(), entrySet() | gezinmek için |
HashMap’in gezinme sırası belirsizdir; sıralı istiyorsan TreeMap, ekleme sırası istiyorsan LinkedHashMap.
Hangi işe hangi yapı?
| İhtiyaç | Seçim | Neden |
|---|---|---|
| İndeksle sık erişim | ArrayList | get(i) O(1) |
| Uçlardan sık ekleme/silme | ArrayDeque | iki uç O(1), dizi hızında |
| LIFO (geri al, parantez, DFS) | ArrayDeque (push/pop) | yığın |
| FIFO (iş sırası, BFS) | ArrayDeque (offer/poll) | kuyruk |
| Önceliğe göre sırala | PriorityQueue | en küçük önce |
| Anahtarla arama | HashMap | ortalama O(1) |
| Anahtarlar sıralı | TreeMap | O(log n) |
| Gezinirken ortadan çok silme | LinkedList + Iterator | silme O(1) |
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
List<int> | List<Integer> |
list.remove(5) değeri siler | İndeksi siler; değer için Integer.valueOf(5) |
LinkedList’te get(i) döngüsü | for-each, yoksa O(n²) |
Stack ve ArrayDeque aynı sırayla yazar | Stack dip→tepe, ArrayDeque tepe→dip |
Boş kuyrukta remove() null döner | İstisna; null için poll() |
for-each içinde list.remove(x) | Iterator.remove() veya removeIf |
map.put her zaman yeni eleman ekler | Aynı anahtarda değeri günceller |
| HashMap anahtarları sıralı yazar | Sıra belirsiz; TreeMap kullan |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is printed?
List<Integer> list = new ArrayList<>(List.of(10, 20, 30, 40));
list.remove(1);
System.out.println(list);
list.remove(Integer.valueOf(30));
System.out.println(list);Cevabı göster
remove(1) bir int alır, indeks 1'deki elemanı (20) siler. remove(Integer.valueOf(30)) bir nesne alır, değeri 30 olan elemanı siler. List<Integer>'da en çok yanılınan nokta.
What is printed?
List<String> s = new ArrayList<>();
s.add("A"); s.add("B"); s.add("C");
s.add(1, "X");
s.set(0, "Y");
System.out.println(s + " " + s.indexOf("B") + " " + s.contains("A"));Cevabı göster
add(1, X) araya ekler ve sağdakileri kaydırır: [A, X, B, C]. set(0, Y) değiştirir, eleman sayısı aynı kalır: [Y, X, B, C]. B indeks 2'de. A artık yok.
What is printed?
Stack<Integer> st = new Stack<>();
st.push(1); st.push(2); st.push(3);
System.out.println(st.pop() + " " + st.peek() + " " + st);Cevabı göster
pop 3'ü çıkarır, peek 2'yi gösterir. Stack sınıfı Vector'den türediği için toString dizi sırasıyla, yani dipten tepeye yazar: [1, 2].
What is printed? Compare with the previous question.
Deque<Integer> ad = new ArrayDeque<>();
ad.push(1); ad.push(2); ad.push(3);
System.out.println(ad + " peek=" + ad.peek());Cevabı göster
ArrayDeque'de push = addFirst. Eleman önden eklenir, toString önden arkaya yazar: tepeden dibe [3, 2, 1]. Aynı push sırası, Stack ile ters görünüm. İkisinde de pop 3 verir.
What is printed?
Queue<Integer> q = new LinkedList<>();
q.offer(7); q.offer(3); q.offer(5);
System.out.println(q.poll() + " " + q.peek() + " " + q);
q.offer(q.poll());
System.out.println(q);Cevabı göster
FIFO: poll 7'yi çıkarır, peek önü gösterir: 3. Kalan [3, 5]. q.offer(q.poll()) önü arkaya taşır: [5, 3].
q is an empty Queue<Integer> (an ArrayDeque). What happens on each line?
System.out.println(q.poll());
System.out.println(q.peek());
q.remove();Cevabı göster
Queue arayüzünde her işlemin iki versiyonu var: offer/poll/peek boş ya da dolu durumda özel değer döner (false/null), add/remove/element istisna fırlatır. poll ve peek null basar, remove NoSuchElementException fırlatır.
What is printed?
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = nums.iterator();
while (it.hasNext())
if (it.next() % 2 == 0) it.remove();
System.out.println(nums);Cevabı göster
Iterator'ın kendi remove() metodu, next() ile en son döndürülen elemanı güvenle siler. Çiftler gider: [1, 3, 5]. Gezerken silmenin doğru yolu bu (ya da removeIf).
What happens in each case?
List<Integer> a = new ArrayList<>(List.of(1, 2, 3, 4));
for (Integer x : a) if (x == 2) a.remove(x); // (i)
List<Integer> b = new ArrayList<>(List.of(1, 2, 3, 4));
for (Integer x : b) if (x == 3) b.remove(x); // (ii)
System.out.println(b);Cevabı göster
for-each gizli bir Iterator kullanır. Liste dışarıdan değişince sonraki next() ConcurrentModificationException fırlatır: (i). (ii)'de 3 silinince boyut 3 olur ve iterator'ın konumu da 3; hasNext() false döner, next() hiç çağrılmaz ve istisna olmaz. Tesadüfen çalışması koda güvenilebileceği anlamına gelmez; Iterator.remove veya removeIf kullan.
What is printed?
Map<String, Integer> count = new HashMap<>();
for (String w : "to be or not to be".split(" "))
count.put(w, count.getOrDefault(w, 0) + 1);
System.out.println(count.get("to") + " " + count.get("or") + " " + count.get("is") + " size=" + count.size());Cevabı göster
Her kelime için mevcut sayıyı (yoksa 0) alıp 1 ekler. to = 2, be = 2, or = 1, not = 1: 4 farklı anahtar. Olmayan anahtar için get null döner.
What is printed?
Map<String, Integer> m = new HashMap<>();
System.out.println(m.put("ali", 70));
System.out.println(m.put("ali", 85));
m.put("ece", 90);
System.out.println(m.get("ali") + " " + m.size() + " " + m.getOrDefault("can", -1));Cevabı göster
put anahtarın eski değerini döndürür: ilk seferde yok (null), ikinci seferde 70. Aynı anahtar ikinci kez konunca değer değişir, yeni eleman eklenmez: size 2. can yok, varsayılan -1.
Choose the most suitable Java class for each: (a) an undo feature in a text editor, (b) print jobs served in arrival order, (c) looking up a student's grade by student ID, (d) frequent access to the i-th element of a growing list.
Cevabı göster
(a) Geri al = son yapılan ilk geri alınır: LIFO, yığın. (b) İlk gelen ilk basılır: FIFO, kuyruk. (c) Anahtarla arama: HashMap ortalama O(1). (d) İndeksle erişim: ArrayList get(i) O(1); LinkedList'te O(n).
Give the time complexity: (a) ArrayList.get(i), (b) ArrayList.add(0, x), (c) LinkedList.get(i), (d) LinkedList.addFirst(x), (e) HashMap.get(key) on average.
Cevabı göster
ArrayList bir dizidir: indeksle erişim O(1), başa ekleme hepsini kaydırır O(n). LinkedList çift yönlü bağlı listedir: i. elemana yürümek O(n), başa ekleme O(1). Ölçüm: n 10 000 → 40 000 olunca ArrayList.add(0) süresi 3.1 ms → 46.2 ms (yaklaşık 16 kat, toplam O(n²)), LinkedList.addFirst neredeyse sabit kaldı.
Doğrulama: Bütün kod parçaları verify/SEN2211/W09.java içinde JDK 26 ile çalıştırıldı (java W09.java); çıktılar (toString biçimleri, null dönüşleri, fırlatılan istisna türleri) programdan birebir alındı. ArrayList.add(0, x) ile LinkedList.addFirst süreleri n = 10 000, 20 000, 40 000 için ölçülüp büyüme oranı karşılaştırıldı.