Generics ve Type Erasure — Derleyicinin Bildiği, JVM'in Unuttuğu
30 saniyede özet
Her elma bir meyvedir ama elma sepeti bir meyve sepeti değildir. Java da List<Integer>'ı List<Number> yerine kabul etmez. Wildcard bu katılığı esnetir; program çalışırken ise tip bilgisi hiç kalmaz.
Sayıları toplayan bir metot yazdın, ona tam sayılardan oluşan bir liste verdin ve derleyici itiraz etti. Tam sayı bir sayı değil mi? Evet — ama liste başka bir hikâye.
-
Bayt: Sayıları toplayan bir metoda tam sayı listesi verdim. Derleyici kızdı!
-
Sen: Tam sayı da bir sayı ama?
-
Bayt: Tam sayı evet. Ama tam sayı listesi başka bir hikâye. Bir sepet düşün...
-
Bayt: Önce tahmin et, sonra sepeti izle. Çözüm küçücük bir soru işareti.
Elma sepeti neden meyve sepeti değil?
List<Integer> ints'i List<Number> numbers değişkenine atayabilseydin, sonra numbers.add(3.14) yazsaydın ne olurdu? Cevabı göster
ints listesinin içine bir Double girerdi. ints.get(0) bir Integer beklerken Double bulurdu. Derleyici bu yüzden atamayı en baştan reddeder.
Adım adım oku
- Elma sepetinde, yani bir Integer listesinde yalnızca elmalar var.
- Bu sepete meyve sepeti, yani Number listesi etiketi yapıştırılabilseydi...
- ...meyve sepetine armut koymak serbest olduğu için biri içine bir ondalıklı sayı koyabilirdi.
- Elma bekleyen kod armut bulurdu. Derleyici bu yüzden etiketi en baştan reddeder.
Bu kurala invarianceGeneric tiplerin alt tip ilişkisini taşımaması: Integer bir Number olsa da List<Integer> bir List<Number> değildir.Diziler bunun tersine covariant'tır; Integer[] bir Number[] sayılır ve yanlış tip yazıldığında hata derlemede değil, runtime'da ArrayStoreException olarak çıkar.Sözlükte gör → denir. Integer bir Number olsa da, Integer listesi bir Number listesi sayılmaz.
Diziler neden farklı?· istersen atla
Diziler tersini yapar: Integer[] bir Number[] sayılır. Bu yüzden yanlış tip yazarsan hata derlemede değil, program çalışırken ArrayStoreException olarak çıkar.
Kafam karıştı, daha basit anlat
Elma sepetini meyve sepeti diye birine verirsen, o kişi içine armut koyabilir. Java bunu baştan engeller: elma listesi, meyve listesi yerine geçmez.
`void sum(List<Number> list)` metoduna neden List<Integer> geçirilemez?
Okurken extends, yazarken super
Bu katılığı yumuşatmanın yolu wildcardGeneric tipte bilinmeyen bir tip argümanı: `?`, `? extends T` ya da `? super T`. Invariance'ı esnetmenin tek yolu.Sözlükte gör →, yani ? işaretidir. Hangisini seçeceğin, listeyle ne yapacağına bağlı: sadece okuyacak mısın, sadece yazacak mısın?
| Parametre | Kabul ettiği | Okuyabildiğin | Yazabildiğin |
|---|---|---|---|
List<Number> | yalnızca List<Number> | Number | Number |
List<? extends Number> | Integer, Double, Number listeleri | Number | hiçbir şey (null hariç) |
List<? super Integer> | Integer, Number, Object listeleri | yalnızca Object | Integer |
Kısa kural: içinden alacaksan extends, içine koyacaksan super. İngilizcesi “Producer extends, Consumer super” (PECS). JDK’daki Collections.copy(List<? super T> dest, List<? extends T> src) imzası tam olarak bunu uygular.
Kafam karıştı, daha basit anlat
Listeden yalnızca bir şey alacaksan extends yaz. Listeye yalnızca bir şey koyacaksan super yaz. İkisini birden yapacaksan tipi tam olarak yaz.
Bir listeden diğerine eleman kopyalayan metot için en esnek doğru imza hangisi?
Kendin gör
Generics — hangi liste hangi metoda girer?
Tohum 928517static void process(List<Number> list) { Number first = list.get(0);} List<Integer> values = new ArrayList<>();process(values);- Çağrı noktası — bekliyor
- Metot gövdesi — bekliyor
JVM'in gördüğü
bekliyor
Şu an ne oldu?
process(List<Number>) çağrılıyor
Argüman List<Integer>. Derleyici önce çağrıyı, sonra metot gövdesini kontrol edecek.
Görevler0/3
List<Integer> geçir ve içinden Number okuaçık
İpucu
List<Number> parametresi List<Integer> kabul etmez. Hangi wildcard alt tipleri kabul eder?
List<Object>'e Integer ekleaçık
İpucu
Yazdığın liste tüketicidir. Tüketici için hangi wildcard?
? extends listesine yazmaya çalış ve derleyicinin neden reddettiğini göraçık
İpucu
Parametreyi ? extends Number yap, sayısal bir liste geçir ve Integer eklemeyi seç.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat.
List<Integer>,List<Number>parametresine girmiyor. - Parametreyi
? extends Numberyap. Çağrı geçti, Number olarak okumak derleniyor. - İşlemi “Integer ekliyor” yap. Çağrı hâlâ geçiyor, ama gövde derlenmiyor.
? super IntegerveList<Object>seç. Yazmak serbest; okumayı dene ve farkı gör.
Hangi satırlar derlenir?
Satır satır: runtime’da ne kalıyor
Derleyici bütün bu kontrolleri yaptıktan sonra <String> gibi tip bilgilerini siler. Buna type erasureDerleyicinin generic tip argümanlarını kontrol ettikten sonra silmesi. Runtime'da List<String> ile List<Integer> aynı sınıftır.Sözlükte gör → denir: program çalışırken List<String> ile List<Integer> aynı sınıftır.
Tipi hiç yazılmamış bir liste (raw type), bu korumayı tamamen kapatan arka kapıdır.
Hata nerede oluşur, nerede patlar
List<String> names = new ArrayList<>();List raw = names;raw.add(42); String first = names.get(0);Debug
derleyici names bir List<String>. Derleyici her ekleme ve okumayı buna göre denetler.
- names
- = []
Sol/sağ ok tuşlarıyla da gezebilirsin.
Bu duruma heap pollutionGeneric tipli bir değişkenin, o tipe uymayan bir nesneyi göstermesi. Genelde raw type ya da unchecked cast yüzünden olur, hata çok sonra ClassCastException olarak çıkar.Sözlükte gör → denir. Hata, yanlış şeyin listeye girdiği yerde değil, çok sonra ve çok uzaktaki bir okumada patlar.
Kafam karıştı, daha basit anlat
Derleyici tipleri kontrol eder, sonra etiketleri söker. Program çalışırken yalnızca “bir liste” kalır. Bu yüzden yanlış tip, çok sonra ve başka bir yerde ortaya çıkar.
Type erasure ne demektir?
Program ne yazdırır?
Tuzaklar
Raw type ve “unchecked” uyarısı. İkisi de hatayı derlemeden çalışma anına erteler. Uyarıyı susturmak yerine tipi düzelt.
new T() yazamamak. Tip bilgisi silindiği için program çalışırken T’nin ne olduğunu bilmez. Nesneyi üretecek bilgiyi dışarıdan ver: Class<T> ya da Supplier<T>.
Aynı imzaya silinen metotlar· istersen atla
save(List<User>) ve save(List<Order>) silindikten sonra ikisi de save(List) olur, bu yüzden ikisi birlikte derlenmez.
List<Object> yerine List<?>. “Herhangi bir liste” demek istiyorsan List<?> yaz. List<Object> yalnızca List<Object> kabul eder.
Son satır ClassCastException fırlatıyor. Hatanın asıl kaynağı hangi satırlar?
Aynı sınıfta `void save(List<User> users)` ve `void save(List<Order> orders)` tanımlamak neden derlenmez?
Aşağıdaki örnek bir bankanın depo katmanından: tip parametreli arayüz, sınırlandırılmış tip, PECS kuralıyla esnek yardımcı metotlar ve erasure yüzünden gereken tip jetonu.
Derinleş · Bankanın depo katmanında generics: uçtan uca 5 dosya · ~87 satır · ilk okumada atlayabilirsin
Kendini sına
Integer bir Number olduğu için Integer listesi de bir Number listesi sayılır.
Generic bir `Repository<T>` içinde yeni bir T nesnesi oluşturman gerekiyor. `new T()` derlenmiyor. Hangisi doğru çözüm?
Aklında kalacak üç şey
- 1 List<Integer> bir List<Number> değildir. Olsaydı içine Double eklenebilir ve Integer bekleyen kod patlardı.
- 2 Kısa kural: sadece okuduğun liste ? extends, sadece yazdığın liste ? super. İkisini de yapıyorsan tam tipi yaz.
- 3 Tip bilgisi derlemeden sonra silinir. Raw type bu korumayı kapatır ve hatayı çok uzaktaki bir ClassCastException'a erteler.
5 kart sonraki derste seni bekliyor