SEN1002 · 14. hafta · Generic Classes and Methods

Generics: generic metotlar, generic sınıflar, wildcard

Tipi parametre olarak alan metot ve sınıflar: <T>, sınırlı tipler (T extends Comparable<T>), ArrayList<T> ve wildcard'lara giriş. Derleme hatası sorularının son büyük kaynağı.

genericstype parametergeneric methodgeneric classtype argumentdiamond operatorbounded type parameterextendsComparablewildcardupper bounded wildcardlower bounded wildcardtype erasureraw typeautoboxing

Generics, bir sınıfı ya da metodu belirli bir tipe bağlamadan yazıp, kullanırken tipini söylemektir. ArrayList<String> yazdığında zaten generic bir sınıf kullanıyorsun. Bu hafta kendi generic metotlarını ve sınıflarını yazıyorsun. Sınavda bu konu çoğunlukla derleme hataları olarak gelir (ilkel tip argümanı, sınırsız T’de compareTo, List<Integer> ile List<Number> uyumsuzluğu) ve “write a generic method / class” sorusu olarak.

Altın kurallar:

  1. Tip parametresi sadece referans tip olur. int değil Integer.
  2. Generic metotta <T> dönüş tipinden önce yazılır: static <T> void print(T[] a).
  3. Sınırsız T’de sadece Object metotları çağrılır. compareTo için <T extends Comparable<T>>.
  4. List<Integer> bir List<Number> değildir. Esneklik için wildcard: List<? extends Number>.
  5. Tip silme (type erasure): çalışma zamanında T yoktur; new T() yazılamaz.

Neden generics?

Generics olmadan ortak kod yazmanın tek yolu her şeyi Object olarak tutmaktı. Bunun iki sorunu var: her okumada cast gerekir ve yanlış tip ancak çalışırken (ClassCastException) ortaya çıkar. Generics bu hatayı derleme zamanına taşır.

Box<String> bs = new Box<>("hello");
String s = bs.get();          // cast yok
Integer n = bs.get();         // DERLENMEZ: String cannot be converted to Integer

Generics’ten önceki “raw type” kullanımının neden tehlikeli olduğunu gösteren deneme: tipsiz bir listeye int koyup String listesi gibi okumak derlenir (sadece uyarı verir) ama çalışırken patlar.

List raw = new ArrayList<String>();   // raw type: uyarı
raw.add(5);
List<String> typed = raw;
String first = typed.get(0);          // çalışırken
ClassCastException

Generic metotlar

Tip parametresi listesi (<T>) dönüş tipinden önce yazılır. T’nin ne olacağını derleyici çağrıdaki argümanlardan çıkarır (type inference).

static <T> void printArray(T[] items) {
    for (T item : items) System.out.print(item + " ");
    System.out.println();
}
static <T> int countMatches(T[] arr, T target) {
    int n = 0;
    for (T x : arr) if (x.equals(target)) n++;     // equals Object'te var: serbest
    return n;
}
Integer[] ints = {1, 2, 3};
String[] strs = {"a", "b"};
Double[] ds = {1.5, 2.5};
printArray(ints);
printArray(strs);
printArray(ds);
System.out.println(countMatches(new String[]{"x", "y", "x"}, "x"));
1 2 3 
a b 
1.5 2.5 
2

Dizi int[] olsaydı printArray ile çağrılamazdı: T bir referans tip olmalı ve int[] bir T[] değildir. Bu yüzden örneklerde Integer[] kullandık.

Sınırlı tip parametreleri (bounded types)

Sınırsız bir T için derleyici sadece Object’in metotlarını (equals, toString, hashCode) bilir. T üzerinde compareTo çağırmak istiyorsan T’nin Comparable olduğunu söylemelisin:

static <T extends Comparable<T>> T maximum(T a, T b, T c) {
    T max = a;
    if (b.compareTo(max) > 0) max = b;
    if (c.compareTo(max) > 0) max = c;
    return max;
}
System.out.println(maximum(3, 9, 4));
System.out.println(maximum("pear", "apple", "zebra"));
System.out.println(maximum(2.5, -1.0, 2.4));
9
zebra
2.5

Sınır bir arayüz olsa bile extends yazılır (implements değil). Comparable olmayan bir sınıfla maximum çağırırsan derleme hatası alırsın: inference variable T has incompatible bounds.

Generic sınıflar

Tip parametresi sınıf adının yanında tanımlanır ve sınıfın her yerinde kullanılabilir.

class Box<T> {
    private T content;
    Box(T content) { this.content = content; }
    T get() { return content; }
    void set(T content) { this.content = content; }
    boolean isEmpty() { return content == null; }
    @Override public String toString() { return "Box[" + content + "]"; }
}

Box<String> bs = new Box<>("hello");      // <> diamond: tip sağda tekrar yazılmaz
Box<Integer> bi = new Box<>(42);
String s = bs.get();
int v = bi.get() + 8;                     // Integer → int (unboxing)
System.out.println(bs + " " + bi + " " + s.length() + " " + v);
bi.set(null);
System.out.println(bi.isEmpty());
Box[hello] Box[42] 5 50
true

Birden fazla tip parametresi virgülle ayrılır. Gelenek olarak tek büyük harf kullanılır: T (type), E (element), K (key), V (value), N (number).

class Pair<K, V> {
    private final K key;
    private final V value;
    Pair(K k, V v) { key = k; value = v; }
    K getKey() { return key; }
    V getValue() { return value; }
    Pair<V, K> swap() { return new Pair<>(value, key); }
    @Override public String toString() { return "(" + key + ", " + value + ")"; }
}
Pair<String, Integer> p = new Pair<>("age", 21);
Pair<Integer, String> q = p.swap();
System.out.println(p + " " + q + " " + (q.getKey() + 1));
(age, 21) (21, age) 22

Sınıf düzeyinde de sınır konabilir: class Stats<T extends Number> içinde x.doubleValue() çağrılabilir. Stats<Integer>’a 4, 5, 9 eklenince ortalama 6.0 çıktı. Stats<String> ise derlenmez.

ArrayList kısa tekrar

MetotNe yapar?
add(x) / add(i, x)Sona / i indeksine ekler
get(i) / set(i, x)Okur / değiştirir
remove(int i)i indeksindeki elemanı siler
remove(Object o)Değeri o olan ilk elemanı siler
size(), isEmpty()Eleman sayısı, boş mu
contains(x), indexOf(x)Var mı, ilk indeksi (yoksa −1)
ArrayList<Integer> list = new ArrayList<>();
list.add(5); list.add(10); list.add(15);
list.add(1, 7);                      // [5, 7, 10, 15]
list.remove(Integer.valueOf(10));    // değer: [5, 7, 15]
list.remove(0);                      // indeks: [7, 15]
System.out.println(list + " size=" + list.size() + " get(1)=" + list.get(1));
System.out.println(list.contains(15) + " " + list.indexOf(15));
[7, 15] size=2 get(1)=15
true 1

TUZAK · remove(1) hangisi? ArrayList<Integer>’da remove(1) argümanı int olduğu için indeks sürümünü çağırır. [10, 20, 30] listesinde remove(1) 20’yi siler, sonuç [10, 30]. Değeri 1 olan elemanı silmek için remove(Integer.valueOf(1)).

Wildcard’lara giriş

Generics değişmezdir (invariant): Integer, Number’ın alt sınıfı olsa da List<Integer> bir List<Number> değildir. Öyle olsaydı, List<Number> gibi davranan bir Integer listesine Double ekleyebilirdin ve liste bozulurdu. Esneklik için soru işareti (wildcard) kullanılır:

YazımKabul ettiğiOkumaEkleme
List<Number>Sadece ListNumberNumber ve alt tipleri
List<? extends Number>List, List, List …Number olarakHayır (null hariç)
List<? super Integer>List, List, ListObject olarakInteger
List<?>Her tipte listeObject olarakHayır
static double sum(List<? extends Number> list) {
    double s = 0;
    for (Number n : list) s += n.doubleValue();
    return s;
}
static void printAll(List<?> list) {
    for (Object o : list) System.out.print(o + ";");
    System.out.println();
}
static void addNumbers(List<? super Integer> list) {
    list.add(1); list.add(2);
}

System.out.println(sum(List.of(1, 2, 3)) + " " + sum(List.of(0.5, 1.5)));
printAll(List.of("x", 1, 2.0));
List<Number> ln = new ArrayList<>();
addNumbers(ln);
List<Object> lo = new ArrayList<>();
addNumbers(lo);
System.out.println(ln + " " + lo);
6.0 2.0
x;1;2.0;
[1, 2] [1, 2]

İPUCU · okuyorsan extends, yazıyorsan super. Bir koleksiyondan sadece veri okuyorsan ? extends T, içine sadece veri koyuyorsan ? super T. Bu derste sınavda çoğunlukla ? extends Number ile toplam ya da ortalama metodu istenir.

Tip silme (type erasure)

Derleyici tip kontrollerini yaptıktan sonra tip argümanlarını siler; çalışma zamanında Box<String> ile Box<Integer> aynı Box sınıfıdır:

Box<String> b1 = new Box<>("x");
Box<Integer> b2 = new Box<>(1);
System.out.println(b1.getClass() == b2.getClass());
System.out.println(b1.getClass().getSimpleName());
true
Box

Bu yüzden generic kodda yazamayacağın şeyler:

  • new T() ve new T[10]: unexpected type; required: class, found: type parameter T
  • static T value;: static alan bütün Box’lar için ortak, hangi T olduğu belli değil
  • obj instanceof Box<String>
  • İlkel tip argümanı: Box<int>

Sık yapılan hatalar

Yanlış düşünceDoğrusu
ArrayList<int>ArrayList<Integer>; ilkel tip olamaz
static T void print(...)<T> dönüş tipinden önce: static <T> void print(...)
Sınırsız T’de a.compareTo(b)<T extends Comparable<T>> gerekir
Sınır arayüzse implements yazılırHer zaman extends
List<Integer> bir List<Number>’dırDeğil; List<? extends Number> kullan
? extends Number listesine eleman eklenirEklenemez, sadece okunur
new T() ile nesne yaratılırTip silme yüzünden yazılamaz
remove(1) değeri 1 olanı silerİndeks 1’i siler
Generics çalışma zamanında tip kontrolü yaparKontrol derleme zamanında; çalışmada tip bilgisi silinmiştir

Kendini test et

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

Soru 1kolay

What is printed?

static <T extends Comparable<T>> T maximum(T a, T b, T c) {
    T max = a;
    if (b.compareTo(max) > 0) max = b;
    if (c.compareTo(max) > 0) max = c;
    return max;
}
// in main:
System.out.println(maximum(3, 9, 4));
System.out.println(maximum("pear", "apple", "zebra"));
System.out.println(maximum(2.5, -1.0, 2.4));
Cevabı göster
9, zebra, 2.5

Aynı metot üç farklı tiple çalışıyor; T her çağrıda argümanlardan çıkarılır (Integer, String, Double). İnt'ler otomatik olarak Integer'a kutulanır (autoboxing). String'lerde compareTo sözlük sırasıdır: zebra en sonda, yani en büyük.

Soru 2orta

Find the compile error.

static <T> T biggest(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}
Cevabı göster
T sınırsız: derleyici T'de compareTo olduğunu bilmiyor

Sınırsız T için derleyici sadece Object metotlarına izin verir. javac: cannot find symbol: method compareTo(T), location: variable a of type T. Çözüm: tipi sınırla, static <T extends Comparable<T>> T biggest(T a, T b).

Soru 3kolay

Find the compile error.

ArrayList<int> list = new ArrayList<>();
Cevabı göster
Tip argümanı ilkel tip olamaz: ArrayList<Integer>

Generics sadece referans tiplerle çalışır. javac: unexpected type; required: reference, found: int. Sarmalayıcı (wrapper) sınıf kullan: Integer, Double, Character, Boolean. list.add(5) yazınca 5 otomatik Integer olur.

Soru 4orta

What is printed?

class Pair<K, V> {
    private final K key; private final V value;
    Pair(K k, V v) { key = k; value = v; }
    K getKey() { return key; }
    Pair<V, K> swap() { return new Pair<>(value, key); }
    public String toString() { return "(" + key + ", " + value + ")"; }
}
// in main:
Pair<String, Integer> p = new Pair<>("age", 21);
Pair<Integer, String> q = p.swap();
System.out.println(p + " " + q + " " + (q.getKey() + 1));
Cevabı göster
(age, 21) (21, age) 22

swap anahtar ile değeri yer değiştirip tipleri de çevirir: Pair<Integer, String>. q.getKey() artık Integer döner, cast'siz toplanabilir: 21 + 1 = 22. Parantez önemli; olmasaydı String birleştirme yapılırdı ve "211" basılırdı.

Soru 5zor

Find the compile error.

static double sum(List<Number> list) { ... }
// in main:
List<Integer> li = new ArrayList<>();
sum(li);
Cevabı göster
List<Integer> bir List<Number> değildir

Integer, Number'ın alt sınıfı olsa da List<Integer>, List<Number>'ın alt tipi değildir (generics invariant). javac: incompatible types: List<Integer> cannot be converted to List<Number>. Çözüm: sum(List<? extends Number> list).

Soru 6zor

Find the compile error.

static void fill(List<? extends Number> list) {
    list.add(3);
}
Cevabı göster
? extends listesine eleman eklenemez

List<? extends Number> bir List<Double> da olabilir; derleyici 3'ün (Integer) oraya güvenle girebileceğini bilemez. javac: int cannot be converted to CAP#1. extends wildcard'ı okumak içindir. Eklemek istiyorsan List<? super Integer> kullan.

Soru 7orta

What is printed?

ArrayList<Integer> list = new ArrayList<>();
list.add(5); list.add(10); list.add(15);
list.add(1, 7);
list.remove(Integer.valueOf(10));
list.remove(0);
System.out.println(list + " size=" + list.size());
Cevabı göster
[7, 15] size=2

[5, 10, 15] → add(1, 7) indeks 1'e ekler: [5, 7, 10, 15]. remove(Integer.valueOf(10)) nesneyi (değeri 10 olanı) siler: [5, 7, 15]. remove(0) int parametreli sürüm: indeks 0'ı siler: [7, 15].

Soru 8orta

What is printed?

ArrayList<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1);
System.out.println(nums);
Cevabı göster
[10, 30]

remove(1) argümanı int olduğu için remove(int index) seçilir: indeks 1'deki 20 silinir. 1 değerini silmek isteseydin remove(Integer.valueOf(1)) yazman gerekirdi. ArrayList<Integer>'ın en sık tuzağı.

Soru 9orta

Find the compile error.

class Box<T> {
    T make() { return new T(); }
}
Cevabı göster
new T() yazılamaz

Tip silme (type erasure) yüzünden çalışma zamanında T'nin ne olduğu bilinmez, nesnesi yaratılamaz. javac: unexpected type; required: class, found: type parameter T. Aynı sebeple new T[10] ve static T alanı da yazılamaz.

Soru 10zor

What is printed?

Box<String> b1 = new Box<>("x");
Box<Integer> b2 = new Box<>(1);
System.out.println(b1.getClass() == b2.getClass());
Cevabı göster
true

Tip silme: derleyici tip kontrolünü yaptıktan sonra tip argümanlarını siler. Çalışma zamanında ikisi de aynı Box sınıfıdır. Bu yüzden b1 instanceof Box<String> gibi kontroller ve new T() mümkün değil.

Soru 11orta

Which method header accepts List<Integer>, List<Double> and List<Number> and lets you call doubleValue() on each element?

  1. double sum(List<Number> list)
  2. double sum(List<Object> list)
  3. double sum(List<? extends Number> list)
  4. double sum(List<? super Number> list)
Cevabı göster
double sum(List<? extends Number> list)

? extends Number: Number ya da herhangi bir alt sınıfının listesi; elemanlar Number olarak okunur, doubleValue çağrılabilir. Denemede List.of(1, 2, 3) için 6.0, List.of(0.5, 1.5) için 2.0. List<Number> sadece tam olarak List<Number> kabul eder. ? super Number okunan elemanı Object olarak verir.

Soru 12zor

Write a generic class Stack<E> backed by an ArrayList<E> with push(E e), pop(), peek(), isEmpty() and size(). pop and peek throw NoSuchElementException("stack is empty") on an empty stack. After pushing a, b, c: pop() + peek() + size() gives cb2.

Cevabı göster
class Stack<E> { private final ArrayList<E> items ... }

Tip parametresi sınıf adının yanında bir kez tanımlanır, sonra alanlarda ve metotlarda E olarak kullanılır. Son eleman yığının tepesi:

import java.util.ArrayList;
import java.util.NoSuchElementException;

public class Stack<E> {
    private final ArrayList<E> items = new ArrayList<>();

    public void push(E e) { items.add(e); }

    public E pop() {
        if (items.isEmpty()) throw new NoSuchElementException("stack is empty");
        return items.remove(items.size() - 1);
    }
    public E peek() {
        if (items.isEmpty()) throw new NoSuchElementException("stack is empty");
        return items.get(items.size() - 1);
    }
    public boolean isEmpty() { return items.isEmpty(); }
    public int size() { return items.size(); }
}
a, b, c sonrası pop "c" döner, peek "b", size 2: cb2. Stack<Integer>'a 1, 4, 9 itip boşalana kadar pop edersek toplam 14.

Doğrulama: Tüm kod örnekleri ve cevaplar JDK 26 ile çalıştırıldı (java verify/SEN1002/W14.java); derleme hataları javac ile denendi (verify/SEN1002/err/E14a-h.java).

Deftere not kartı

Generic = tipi parametre olarak almak. Derleme zamanında tip güvenliği, cast gerekmez. Generic metot: static <T> void print(T[] a) → <T> dönüş tipinden ÖNCE. Generic sınıf: class Box<T> { T value; } kullanım: Box<String> b = new Box<>("x"); (<> diamond) Birden çok parametre: class Pair<K, V>. Gelenek: T tip, E eleman, K anahtar, V değer. Tip argümanı İLKEL OLAMAZ: ArrayList<int> hata → ArrayList<Integer> (autoboxing). Sınırlı tip: <T extends Comparable<T>> → compareTo çağrılabilir. Sınırsız T'de sadece Object metotları. <T extends Number> → doubleValue() çağrılabilir. Arayüz için de "extends" yazılır. List<Integer> bir List<Number> DEĞİLDİR. ? extends Number → oku (Number olarak), EKLEME yok. ? super Integer → Integer ekle. List<?> → her tipte liste, elemanlar Object olarak okunur. Type erasure: çalışmada tip yok → new T(), new T[n], static T alan, instanceof Box<String> YOK. ArrayList<Integer>.remove(1) → indeks 1'i siler. Değer için remove(Integer.valueOf(1)). ArrayList: add(x), add(i,x), get(i), set(i,x), remove, size(), contains, indexOf, isEmpty.