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ğı.
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:
- Tip parametresi sadece referans tip olur.
intdeğilInteger. - Generic metotta
<T>dönüş tipinden önce yazılır:static <T> void print(T[] a). - Sınırsız T’de sadece Object metotları çağrılır. compareTo için
<T extends Comparable<T>>. List<Integer>birList<Number>değildir. Esneklik için wildcard:List<? extends Number>.- 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
| Metot | Ne 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>’daremove(1)argümanı int olduğu için indeks sürümünü çağırır.[10, 20, 30]listesinderemove(1)20’yi siler, sonuç[10, 30]. Değeri 1 olan elemanı silmek içinremove(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ım | Kabul ettiği | Okuma | Ekleme |
|---|---|---|---|
List<Number> | Sadece List | Number | Number ve alt tipleri |
List<? extends Number> | List | Number olarak | Hayır (null hariç) |
List<? super Integer> | List | Object olarak | Integer |
List<?> | Her tipte liste | Object olarak | Hayı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 Numberile 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()venew T[10]:unexpected type; required: class, found: type parameter Tstatic T value;: static alan bütün Box’lar için ortak, hangi T olduğu belli değilobj instanceof Box<String>- İlkel tip argümanı:
Box<int>
Sık yapılan hatalar
| Yanlış düşünce | Doğ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ır | Her zaman extends |
List<Integer> bir List<Number>’dır | Değil; List<? extends Number> kullan |
? extends Number listesine eleman eklenir | Eklenemez, sadece okunur |
new T() ile nesne yaratılır | Tip silme yüzünden yazılamaz |
remove(1) değeri 1 olanı siler | İndeks 1’i siler |
| Generics çalışma zamanında tip kontrolü yapar | Kontrol 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ç.
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
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.
Find the compile error.
static <T> T biggest(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}Cevabı göster
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).
Find the compile error.
ArrayList<int> list = new ArrayList<>();Cevabı göster
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.
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
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ı.
Find the compile error.
static double sum(List<Number> list) { ... }
// in main:
List<Integer> li = new ArrayList<>();
sum(li);Cevabı göster
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).
Find the compile error.
static void fill(List<? extends Number> list) {
list.add(3);
}Cevabı göster
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.
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
[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].
What is printed?
ArrayList<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1);
System.out.println(nums);Cevabı göster
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ğı.
Find the compile error.
class Box<T> {
T make() { return new T(); }
}Cevabı göster
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.
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
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.
Which method header accepts List<Integer>, List<Double> and List<Number> and lets you call doubleValue() on each element?
- double sum(List<Number> list)
- double sum(List<Object> list)
- double sum(List<? extends Number> list)
- double sum(List<? super Number> list)
Cevabı göster
? 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.
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
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).