SEN2211 · 9. hafta · Data Structure Classes in Java

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.

Java Collections FrameworkListArrayListLinkedListArrayDequeStackQueue interfaceDeque interfaceIteratorConcurrentModificationExceptionHashMapgenericsautoboxing

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:

  1. Değişkeni arayüz tipiyle tanımla. List<Integer> a = new ArrayList<>();
  2. Yığın ve kuyruk için ArrayDeque. Stack sınıfı eskidir.
  3. İndeksle erişim çoksa ArrayList, uçlardan ekleme çoksa ArrayDeque/LinkedList.
  4. Gezerken silme: Iterator.remove() ya da removeIf. for-each içinde list.remove yapma.
  5. Anahtarla arama HashMap.

Collections Framework’ün haritası

Arayüz (interface)Ne sağlarSınıflar (class)
List<E>sıralı, indeksli, tekrar olabilirArrayList, LinkedList
Queue<E>FIFO: offer, poll, peekArrayDeque, LinkedList, PriorityQueue
Deque<E>iki uçlu: addFirst, pollLast, push, popArrayDeque, LinkedList
Map<K,V>anahtar → değerHashMap, TreeMap, LinkedHashMap
Set<E>tekrar yokHashSet, 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) ve remove(Object). List<Integer> üzerinde list.remove(1) indeks 1’i siler. Değeri 1 olan elemanı silmek için list.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

  1. haftadaki DoublyLinkedList’in hazır hali. Hem List hem Deque arayü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 Stack dipten tepeye ([1, 2, 3]), ArrayDeque tepeden 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ırnull / false döner
Ekleadd(e)offer(e)
Çıkarremove()poll()
Bakelement()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}
MetotNe 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çimNeden
İndeksle sık erişimArrayListget(i) O(1)
Uçlardan sık ekleme/silmeArrayDequeiki 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ıralaPriorityQueueen küçük önce
Anahtarla aramaHashMaportalama O(1)
Anahtarlar sıralıTreeMapO(log n)
Gezinirken ortadan çok silmeLinkedList + Iteratorsilme 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 yazarStack 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 eklerAynı anahtarda değeri günceller
HashMap anahtarları sıralı yazarSıra belirsiz; TreeMap kullan

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1orta

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
[10, 30, 40] / [10, 40]

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.

Soru 2kolay

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
[Y, X, B, C] 2 false

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.

Soru 3orta

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
3 2 [1, 2]

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].

Soru 4zor

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
[3, 2, 1] peek=3

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.

Soru 5kolay

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
7 3 [3, 5] / [5, 3]

FIFO: poll 7'yi çıkarır, peek önü gösterir: 3. Kalan [3, 5]. q.offer(q.poll()) önü arkaya taşır: [5, 3].

Soru 6orta

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
null, null, NoSuchElementException

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.

Soru 7orta

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
[1, 3, 5]

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).

Soru 8zor

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
(i) ConcurrentModificationException; (ii) no exception, prints [1, 2, 4]

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.

Soru 9orta

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
2 1 null size=4

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.

Soru 10orta

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
null / 70 / 85 2 -1

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.

Soru 11kolay

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) ArrayDeque as a stack, (b) ArrayDeque/LinkedList as a Queue, (c) HashMap, (d) ArrayList

(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).

Soru 12kolay

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
(a) O(1) (b) O(n) (c) O(n) (d) O(1) (e) O(1)

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ı.

Deftere not kartı

ARAYÜZ ← SINIF: List ← ArrayList, LinkedList | Queue/Deque ← ArrayDeque, LinkedList | Map ← HashMap, TreeMap ArrayList: get/set O(1), sona add O(1)*, add(0,x)/remove(0) O(n) LinkedList: addFirst/addLast/removeFirst/removeLast O(1), get(i) O(n) list.remove(1) → İNDEKS 1; list.remove(Integer.valueOf(1)) → DEĞER 1 YIĞIN: Deque<Integer> s = new ArrayDeque<>(); push/pop/peek (Stack eski; toString dip→tepe) ArrayDeque toString önden arkaya: push 1,2,3 → [3, 2, 1] KUYRUK: Queue<Integer> q = new ArrayDeque<>(); offer/poll/peek Boşken: poll/peek → null | remove/element/pop → NoSuchElementException | Stack.pop → EmptyStackException ArrayDeque null kabul etmez (NullPointerException) for-each içinde list.remove → ConcurrentModificationException; it.remove() veya removeIf kullan HashMap: put (eski değeri döner), get (yoksa null), getOrDefault, containsKey, remove; ortalama O(1); sıra YOK TreeMap: anahtar sıralı; sayaç kalıbı: m.put(k, m.getOrDefault(k, 0) + 1) Generics: int değil Integer (autoboxing)