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>parametresiList<Integer>veList<Double>kabul eder.? super T— "T veya üst tipi". Listeye yazmak için uygundur (Consumer).List<? super Integer>parametresiList<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ılabilir1// 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}Ornek2
çalıştırılabilir1// 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}Ornek3
çalıştırılabilir1// 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}