İçeriğe geç

Generics ve Type Erasure — Derleyicinin Bildiği, JVM'in Unuttuğu

Orta 8 dk Çok sık karşılaşılır

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.

  1. Bayt: Sayıları toplayan bir metoda tam sayı listesi verdim. Derleyici kızdı!

  2. Sen: Tam sayı da bir sayı ama?

  3. Bayt: Tam sayı evet. Ama tam sayı listesi başka bir hikâye. Bir sepet düşün...

  4. 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.

Elma sepetine meyve sepeti demek, armuda kapı açmaktır.
Adım adım oku
  1. Elma sepetinde, yani bir Integer listesinde yalnızca elmalar var.
  2. Bu sepete meyve sepeti, yani Number listesi etiketi yapıştırılabilseydi...
  3. ...meyve sepetine armut koymak serbest olduğu için biri içine bir ondalıklı sayı koyabilirdi.
  4. 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.

Hızlı kontrolOrta

`void sum(List<Number> list)` metoduna neden List<Integer> geçirilemez?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

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?

ParametreKabul ettiğiOkuyabildiğinYazabildiğin
List<Number>yalnızca List<Number>NumberNumber
List<? extends Number>Integer, Double, Number listeleriNumberhiçbir şey (null hariç)
List<? super Integer>Integer, Number, Object listeleriyalnızca ObjectInteger

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.

Hızlı kontrolOrta

Bir listeden diğerine eleman kopyalayan metot için en esnek doğru imza hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Generics — hangi liste hangi metoda girer?

Tohum 928517
Variance.java
1static void process(List<Number> list) {
2 Number first = list.get(0);
3}
4
5List<Integer> values = new ArrayList<>();
6process(values);
Java 21UTF-8LF
  • Çağrı noktası — bekliyor
  • Metot gövdesi — bekliyor

JVM'in gördüğü

bekliyor

Hız
Adım 0

Ş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.

  1. Varsayılanla oynat. List<Integer>, List<Number> parametresine girmiyor.
  2. Parametreyi ? extends Number yap. Çağrı geçti, Number olarak okumak derleniyor.
  3. İşlemi “Integer ekliyor” yap. Çağrı hâlâ geçiyor, ama gövde derlenmiyor.
  4. ? super Integer ve List<Object> seç. Yazmak serbest; okumayı dene ve farkı gör.
Hızlı kontrolOrta

Hangi satırlar derlenir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Derlenir

Derleyici güvenli olduğunu kanıtlayabiliyor

    Derlenmez

    Derleyici güvenliği kanıtlayamıyor

      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

      Pollution.java
      şu an çalışan satırList<String> names = new ArrayList<>();
      2List raw = names;
      3raw.add(42);
      4
      5String first = names.get(0);

      Debug

      Adım 1/4

      derleyici names bir List<String>. Derleyici her ekleme ve okumayı buna göre denetler.

      names
      = []
      Java 21UTF-8LF1:1

      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.

      Hızlı kontrolBaşlangıç

      Type erasure ne demektir?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Program ne yazdırır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
      SameClass.java
      1List<String> names = new ArrayList<>();
      2List<Integer> ids = new ArrayList<>();
      3
      4System.out.println(names.getClass() == ids.getClass());
      5System.out.println(names.getClass().getName());
      Java 21UTF-8LF

      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.

      Hızlı kontrolOrta

      Son satır ClassCastException fırlatıyor. Hatanın asıl kaynağı hangi satırlar?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Hatalı satıra dokun, sonra kontrol et.

      LegacyFill.java
      Java 21UTF-8LF

      Aynı sınıfta `void save(List<User> users)` ve `void save(List<Order> orders)` tanımlamak neden derlenmez?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      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
      Proje dosyaları

      src/main/java/bank/store/ Repository.java Tip parametreli sözleşme: her entity kendi id tipiyle (hesap IBAN ile, müşteri numarayla).

      src/main/java/bank/store/Repository.java
      public interface Repository<T extends Identifiable<ID>, ID> {
      T save(T entity);
      Optional<T> findById(ID id);
      List<T> findAll();
      }
      interface Identifiable<ID> {
      ID id();
      }

      src/main/java/bank/store/ InMemoryRepository.java Sınırlandırılmış tip: T yalnızca Identifiable olabilir, böylece id'ye erişilebilir.

      src/main/java/bank/store/InMemoryRepository.java
      public class InMemoryRepository<T extends Identifiable<ID>, ID> implements Repository<T, ID> {
      private final Map<ID, T> rows = new ConcurrentHashMap<>();
      @Override
      public T save(T entity) {
      rows.put(entity.id(), entity); // possible only because T is bounded by Identifiable
      return entity;
      }
      @Override
      public Optional<T> findById(ID id) {
      return Optional.ofNullable(rows.get(id));
      }
      @Override
      public List<T> findAll() {
      return List.copyOf(rows.values());
      }
      }

      src/main/java/bank/store/ Copies.java PECS: okuduğun yerde extends, yazdığın yerde super. Aynı metot daha çok tiple çalışır.

      src/main/java/bank/store/Copies.java
      public final class Copies {
      private Copies() {}
      // Producer Extends, Consumer Super.
      // from: anything that PRODUCES T (List<Account>, List<SavingsAccount>…)
      // to: anything that ACCEPTS T (List<Account>, List<Object>…)
      public static <T> void copy(List<? extends T> from, List<? super T> to) {
      for (T item : from) to.add(item);
      }
      // Reads only: extends is enough, and the method now accepts List<Integer>, List<Long>, …
      public static double sum(Collection<? extends Number> numbers) {
      double total = 0;
      for (Number n : numbers) total += n.doubleValue();
      return total;
      }
      }

      src/main/java/bank/store/ JsonStore.java Erasure: çalışma anında List<Account> yalnızca List'tir. Tip bilgisi Class<T> ile taşınır.

      src/main/java/bank/store/JsonStore.java
      public class JsonStore {
      private final ObjectMapper json = new ObjectMapper();
      // `new T()` or `T.class` does not compile: T is erased at runtime.
      // The caller hands over the Class object, which survives erasure.
      public <T> T read(Path file, Class<T> type) throws IOException {
      return json.readValue(file.toFile(), type);
      }
      // For generic types (List<Account>) a Class is not enough: List.class has no element type.
      // A TypeReference captures it in an anonymous subclass's signature.
      public List<Account> readAccounts(Path file) throws IOException {
      return json.readValue(file.toFile(), new TypeReference<List<Account>>() {});
      }
      }

      src/main/java/bank/store/ Main.java Hepsini kullanan program.

      src/main/java/bank/store/Main.java
      public class Main {
      public static void main(String[] args) {
      // Account is keyed by its IBAN; SavingsAccount extends Account with an interest rate.
      Repository<Account, String> accounts = new InMemoryRepository<>();
      accounts.save(new Account("TR330006100519786457841326", new BigDecimal("1250.40")));
      accounts.save(new SavingsAccount("TR320010009999901234567890", new BigDecimal("50000"),
      new BigDecimal("0.42"))); // subtype is fine
      List<Object> audit = new ArrayList<>();
      Copies.copy(accounts.findAll(), audit); // List<Account> -> List<Object>: PECS at work
      System.out.println(audit.size()); // 2
      System.out.println(Copies.sum(List.of(1, 2, 3))); // 6.0 (List<Integer>)
      System.out.println(Copies.sum(List.of(1.5, 2.5))); // 4.0 (List<Double>)
      List<String> ibans = new ArrayList<>();
      List<Integer> customerNos = new ArrayList<>();
      System.out.println(ibans.getClass() == customerNos.getClass()); // true — both are just ArrayList at runtime
      }
      }

      Kendini sına

      Şimşek turu1/5

      Integer bir Number olduğu için Integer listesi de bir Number listesi sayılır.

      Soru 1/2İleri

      Generic bir `Repository<T>` içinde yeni bir T nesnesi oluşturman gerekiyor. `new T()` derlenmiyor. Hangisi doğru çözüm?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 List<Integer> bir List<Number> değildir. Olsaydı içine Double eklenebilir ve Integer bekleyen kod patlardı.
      2. 2 Kısa kural: sadece okuduğun liste ? extends, sadece yazdığın liste ? super. İkisini de yapıyorsan tam tipi yaz.
      3. 3 Tip bilgisi derlemeden sonra silinir. Raw type bu korumayı kapatır ve hatayı çok uzaktaki bir ClassCastException'a erteler.
      Sonraki kapı Lambda içinde bir sayacı artırmaya çalıştın ve derleyici izin vermedi. Neden? Lambda ve Fonksiyonel Arayüzler — Lambda Neyi Yakalar? · 8 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı