Eğitim Portalı/Java/Generics (Jenerikler)
Java01-java/09-generics

Generics (Jenerikler)

Bir önceki bölümde List<String>, Map<String, Integer> gibi yazımlar gördün. Açılı parantez içindeki tipler işte generics'tir. Generics, bir sınıf veya metodu belirli bir tipe bağlamadan, "hangi tiple çalışacağı sonrad…

Generics (Jenerikler)

Bir önceki bölümde List<String>, Map<String, Integer> gibi yazımlar gördün. Açılı parantez içindeki tipler işte generics'tir. Generics, bir sınıf veya metodu belirli bir tipe bağlamadan, "hangi tiple çalışacağı sonradan belirlensin" diyerek yazmamızı sağlar. Faydası ikidir: tip güvenliği (yanlış tip hatasını derleme anında yakalarsın) ve yeniden kullanılabilirlik (aynı kodu her tiple kullanırsın).

Neden generics?

Generics olmadan koleksiyonlar Object tutardı; her okumada elle dönüştürme (cast) yapman ve hata riskini taşıman gerekirdi:

List liste = new ArrayList();
liste.add("metin");
String s = (String) liste.get(0); // elle cast, hataya açık
liste.add(42);                     // derleyici engellemez!

Generics ile derleyici tipi bilir; yanlış ekleme derlenmez, okurken cast gerekmez:

List<String> liste = new ArrayList<>();
liste.add("metin");
String s = liste.get(0); // cast yok
// liste.add(42);        // derleme hatası

Generic sınıf ve generic metot

Kendi generic tiplerini yazabilirsin. Tip parametresini açılı parantezle bildirirsin (geleneksel olarak T, E, K, V harfleri kullanılır):

class Kutu<T> {
    private T icerik;
    T al() { return icerik; }
}
Kutu<String> k = new Kutu<>("merhaba");

Bir metodu da generic yapabilirsin; tip parametresini dönüş tipinden önce bildirirsin:

static <T> T ilk(T[] dizi) { return dizi[0]; }

Örnek 1 (./Ornek1.java) generic bir Kutu<T> sınıfı ve generic yazdir/ilk metotlarını farklı tiplerle kullanır.

Sınırlı tipler (bounded types)

Bazen tip parametresinin "herhangi bir tip" değil, belirli bir tipin alt tipi olmasını isteriz. <T extends Number> der ki: "T, Number veya alt tipi olmalı." Bu sınır sayesinde T üzerinde Number metotlarını (doubleValue() gibi) çağırabilirsin:

static <T extends Number> double toplam(List<T> liste) {
    double t = 0;
    for (T e : liste) t += e.doubleValue();
    return t;
}

Birden çok sınır da koyabilirsin: <T extends Number & Comparable<T>>. Örnek 2 (./Ornek2.java) sayı listeleri üzerinde toplam, ortalama ve en büyüğü hesaplar; String listesi göndermenin neden derlenmediğini açıklar.

Wildcard'lar: ? extends ve ? super

Metot parametrelerinde esneklik için ? (wildcard) kullanırız:

  • ? extends T — "T veya alt tipi". Listeden okumak için uygundur (Producer). List<? extends Number> parametresi List<Integer> ve List<Double> kabul eder.
  • ? super T — "T veya üst tipi". Listeye yazmak için uygundur (Consumer). List<? super Integer> parametresi List<Integer>, List<Number>, List<Object> kabul eder.

Bu seçimi hatırlamanın yolu PECS kuralıdır: Producer Extends, Consumer Super. Veriyi üreten (okuduğun) kaynak için extends, tükettiğin (yazdığın) hedef için super. Örnek 3 (./Ornek3.java) ikisini de uygular ve farkı gösterir.

Type erasure'a kısa bir not

Java generics'i type erasure ile uygular: tip bilgisi derleme sırasında kullanılır, çalışma zamanında "silinir". Yani çalışma anında List<String> ve List<Integer> aslında aynı List tipidir. Bunun pratik sonucu: new T[] yazamazsın, instanceof List<String> yapamazsın. Günlük kullanımda bunları nadiren dert edersin ama farkında olmak iyidir.

Özet

Generics ile tip güvenli ve yeniden kullanılabilir kod yazmayı öğrendik: generic sınıf/metot, sınırlı tipler (extends) ve wildcard'lar (? extends/? super) ile PECS kuralı. Bu bilgi, koleksiyonları ve birazdan göreceğin Stream API'yi rahatça kullanmanın anahtarıdır. Sırada, sabit değer kümeleri için enum ve ilkel tiplerin nesne karşılığı wrapper sınıfları var.

Kod Örnekleri(3)

Ornek1

çalıştırılabilir
Ornek1.java
1// Ornek1: Generic sınıf ve generic metot.
2// Çalıştırma: java Ornek1.java
3public class Ornek1 {
4
5    public static void main(String[] args) {
6        // Aynı Kutu sınıfını farklı tiplerle, tip güvenli biçimde kullanabiliriz.
7        Kutu<String> metinKutu = new Kutu<>("merhaba");
8        Kutu<Integer> sayiKutu = new Kutu<>(42);
9
10        System.out.println("metinKutu içeriği: " + metinKutu.al() + " (uzunluk " + metinKutu.al().length() + ")");
11        System.out.println("sayiKutu içeriği : " + sayiKutu.al() + " (kare " + (sayiKutu.al() * sayiKutu.al()) + ")");
12
13        // Derleyici tip uyumunu kontrol eder; yanlış tip ataması derlenmez:
14        // metinKutu.koy(123);  // <-- hata: String bekleniyor
15
16        // --- Generic metot ---
17        // <T> bildirimi, metodun herhangi bir tiple çalışabileceğini söyler.
18        Integer[] sayilar = {3, 1, 4, 1, 5};
19        String[] kelimeler = {"elma", "armut"};
20        System.out.print("\nSayılar: "); yazdir(sayilar);
21        System.out.print("Kelimeler: "); yazdir(kelimeler);
22
23        System.out.println("\nİlk eleman (sayilar): " + ilk(sayilar));
24        System.out.println("İlk eleman (kelimeler): " + ilk(kelimeler));
25    }
26
27    // Generic metot: T herhangi bir tip olabilir.
28    static <T> void yazdir(T[] dizi) {
29        StringBuilder sb = new StringBuilder("[");
30        for (int i = 0; i < dizi.length; i++) {
31            sb.append(dizi[i]);
32            if (i < dizi.length - 1) sb.append(", ");
33        }
34        System.out.println(sb.append("]"));
35    }
36
37    static <T> T ilk(T[] dizi) {
38        return dizi.length > 0 ? dizi[0] : null;
39    }
40}
41
42// Generic sınıf: T bir tip parametresidir, kullanım anında belirlenir.
43class Kutu<T> {
44    private T icerik;
45    Kutu(T icerik) { this.icerik = icerik; }
46    T al() { return icerik; }
47    void koy(T yeni) { this.icerik = yeni; }
48}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek2

çalıştırılabilir
Ornek2.java
1// Ornek2: Bounded type (sınırlı tip) — <T extends Number>.
2// Çalıştırma: java Ornek2.java
3import java.util.List;
4
5public class Ornek2 {
6
7    // T yalnızca Number ve alt tipleri (Integer, Double, Long...) olabilir.
8    // Bu sınır sayesinde T üzerinde doubleValue() gibi Number metotlarını çağırabiliriz.
9    static <T extends Number> double toplam(List<T> liste) {
10        double t = 0;
11        for (T eleman : liste) {
12            t += eleman.doubleValue();
13        }
14        return t;
15    }
16
17    static <T extends Number> double ortalama(List<T> liste) {
18        return liste.isEmpty() ? 0 : toplam(liste) / liste.size();
19    }
20
21    // Birden çok sınır da konabilir: <T extends Number & Comparable<T>>
22    static <T extends Number & Comparable<T>> T enBuyuk(List<T> liste) {
23        T max = liste.get(0);
24        for (T e : liste) {
25            if (e.compareTo(max) > 0) max = e;
26        }
27        return max;
28    }
29
30    public static void main(String[] args) {
31        List<Integer> tamSayilar = List.of(10, 20, 30, 40);
32        List<Double> ondaliklar = List.of(1.5, 2.5, 3.0);
33
34        System.out.println("Tam sayılar: " + tamSayilar);
35        System.out.println("  toplam   = " + toplam(tamSayilar));
36        System.out.println("  ortalama = " + ortalama(tamSayilar));
37        System.out.println("  en büyük = " + enBuyuk(tamSayilar));
38
39        System.out.println("\nOndalıklar: " + ondaliklar);
40        System.out.println("  toplam   = " + toplam(ondaliklar));
41        System.out.println("  ortalama = " + ortalama(ondaliklar));
42
43        // toplam(List.of("a","b"));  // <-- derlenmez: String, Number değil
44    }
45}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek3

çalıştırılabilir
Ornek3.java
1// Ornek3: Wildcard'lar — ? extends ve ? super (PECS prensibi).
2// Çalıştırma: java Ornek3.java
3import java.util.ArrayList;
4import java.util.List;
5
6public class Ornek3 {
7
8    // ? extends Number: "Number veya alt tipi" olan bir listeden OKURUZ (Producer).
9    // Buraya List<Integer>, List<Double> vb. gönderebiliriz.
10    static double topla(List<? extends Number> liste) {
11        double t = 0;
12        for (Number n : liste) t += n.doubleValue();
13        return t;
14    }
15
16    // ? super Integer: "Integer veya üst tipi" olan bir listeye YAZARIZ (Consumer).
17    // Buraya List<Integer>, List<Number>, List<Object> gönderebiliriz.
18    static void birdenOnaKadarEkle(List<? super Integer> hedef) {
19        for (int i = 1; i <= 5; i++) {
20            hedef.add(i);
21        }
22    }
23
24    public static void main(String[] args) {
25        List<Integer> tamlar = List.of(1, 2, 3);
26        List<Double> ondaliklar = List.of(1.5, 2.5);
27
28        // ? extends ile her ikisinden de okuyabiliriz:
29        System.out.println("topla(tamlar)     = " + topla(tamlar));
30        System.out.println("topla(ondaliklar) = " + topla(ondaliklar));
31
32        // ? super ile farklı üst-tipli listelere yazabiliriz:
33        List<Integer> hedef1 = new ArrayList<>();
34        List<Number> hedef2 = new ArrayList<>();
35        birdenOnaKadarEkle(hedef1);
36        birdenOnaKadarEkle(hedef2);
37        System.out.println("\nInteger listesine eklendi: " + hedef1);
38        System.out.println("Number listesine eklendi : " + hedef2);
39
40        // PECS: Producer Extends, Consumer Super.
41        // OKUYACAKSAN ? extends, YAZACAKSAN ? super kullan.
42        System.out.println("\nKural (PECS): Producer-Extends, Consumer-Super.");
43    }
44}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.