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.