Sorunun geçmişi.
Java sürüm 5'te, eski kodla geriye dönük ikili uyumluluğu sağlamak için tür silinmesi kullanarak genel türleri tanıttı. Ancak diziler yeniden biçimlendirilmiştir - bileşen türlerini (Class) çalışma zamanı sırasında taşırlar ve eleman ekleme sırasında ArrayStoreException kontrollerini zorlarlar. T gibi genel tür parametreleri, bayt kodunda (genellikle Object) sınırlarına silindiğinden, JVM çalışma zamanı sırasında T'yi belirli bir sınıfa çözememekte, derleme zamanı tür sistemi ile çalışma zamanı dizi doğrulaması arasında uzlaşılamaz bir boşluk yaratmaktadır.
Sorun.
Derleyici new T[10] 'i izin verseydi, üretilen bayt kodu Object[] oluşturur, oysa referans değişkeni T[] olarak iddia ederdi. Bu uyumsuzluk, yığın kirlenmesine olanak tanır: bir Integer bir String[] türündeki dizi referansına (aslında bir Object[]'ye işaret eden) kaydedilebilir ve JVM'nin tür korumasını atlatabilir. Bozulma, sonraki bir okuma işlemi sırasında bir ClassCastException tetiklendiğinde görünür hale gelir ve Java'nın statik tür güvenliği garantisini ihlal eder, hata ayıklamayı son derece zor hale getirir.
Çözüm.
Geliştiricilerin, tür güvenli alternatifleri tercih ederek doğrudan oluşturma yapmaktan kaçınmaları gerekir. java.lang.reflect.Array.newInstance(Class<T>, int) yöntemi, doğru çalışma zamanı Class bileşen türü ile bir dizi oluşturur. Alternatif olarak, alma işlemi sırasında açık bir biçimde tür dönüşümü yaparak Object[] kullanabilir (uyarıları @SuppressWarnings("unchecked") ile bastırarak) veya daha iyi bir şekilde dizileri ArrayList<T> veya diğer koleksiyonlarla değiştirebilir. Bu koleksiyonlar tamamen genel tür sistemini benimseyerek çalışma zamanı dizi oluşturma gerektirmez.
Sorun tanımı.
Yüksek performanslı bir lineer cebir kütüphanesi tasarlarken, ekip, Double, Complex ve özel sayısal türleri desteklemek için tür güvenli bir Matrix<T> gerektirdi; ArrayList<T>'nin kutulama yükü olmaksızın. İç depolama, önbellek yerelliği ve ham hız için iki boyutlu bir dizi T[][] gerektiriyordu. Zorluk, kurucu içinde T[][]'yi oluştururken derleyici hatalarını tetiklememek ya da sayısal sonuçları bozabilecek ince tür güvenliği zayıflıkları introduce olmaktan kaçınmaktı.
Çözüm 1: Object[] dizisinin kontrolsüz dönüşümü.
Bir öneri, (T[][]) new Object[rows][cols] şeklinde dönüşüm yapmayı ve kontrolsüz uyarıyı anotasyonlarla bastırmayı içeriyordu. Bu yaklaşım sıfır performans yükü ve doğrudan bellek yerleşimi kontrolü sundu. Ancak, kırılgan bir sözleşme oluşturdu: Eğer Matrix iç dizisini bir alıcı aracılığıyla açığa çıkarırsa, dış kod uyumsuz türler ekleyerek yığınları kirletebilir, bu da neredeyse geri izlenemez ClassCastException hatalarına yol açabilir.
Çözüm 2: Object depolama ile her bir öğe için dönüşüm.
Diğer bir seçenek, verileri Object[][] olarak saklamak ve her okuma işlemi sırasında bireysel öğeleri T'ye dönüştürmekti. Bu, tür uyumsuzluklarının alım noktasında hemen tespit edilmesini güvence altına alarak hata ayıklamayı önemli ölçüde basit hale getirdi. Dezavantajı ise önemli ölçüde fazla kod ve sıkı hesaplama döngüleri içinde tekrar eden checkcast bayt kodu talimatları nedeniyle ölçülebilir %5-10 performans cezasıydı; bu da kütüphanenin yerel dizilerin performansını karşılama amacını boşa çıkardı.
Çözüm 3: Array.newInstance() kullanarak yansıtma.
Ekip nihayetinde Array.newInstance(componentType, rows, cols) kullandı ve çağrıcıların bir Class<T> token'ı sağlamasını gerektirdi. Bu, tam olarak doğru çalışma zamanı türü ile bir dizi oluşturdu ve yığın kirlenmesini tamamen önleyerek yerel dizilerin ham hızını korudu. Matris oluşturma sırasında yansıtmalı olarak oluşturmanın bir kez ki maliyeti, matris işlemlerinin O(n³) hesaplama yükü ile karşılaştırıldığında önemsizdi ve çözüm, güvensiz dönüşümler ya da erişim başına yük olmadan derleme zamanı tür güvenliği sağladı.
Sonuç.
Kütüphane, üç yıllık yoğun kullanım sırasında sıfır rapor edilen ArrayStoreException ya da ClassCastException hatalarıyla sevk edildi. Yansıtma yaklaşımı, hem ilkel sarıcıları hem de karmaşık özel türleri zahmetsizce desteklerken, sıkı tür denetimi kritik finansal hesaplamalarda sessiz veri bozulmasını önledi. Performans kıyaslamaları, bir kez yansıtma için yükün matris işlemlerinin hesaplama maliyetine kıyasla önemsiz kaldığını doğruladı.
**Wildcard dizisi List<?>[]** neden **List<String>[]**'yi etkileyen tür güvenliği tuzaklarından kaçınırken, her ikisi de parametreleştirilmiş türlerin dizileri olmasına rağmen?** **List<?>[], bileşen türlerinin uyumunu doğrulayamayan kritik bir kısıtlama nedeniyle hiçbir niteliksiz eleman ekleyemeyeceğiniz bir ham tür dizisi olarak işlenir. List<String>[] her bir elemanın List<String> olacağını garanti eden bir dizi anlamına gelir, ancak silindikten sonra, JVM yalnızca List[] görür. Eğer izin verilseydi, bir List<Integer> dizinin elemanına atanan bir öğeye karşılık gelecekti (çünkü çalışma zamanı boyunca yalnızca List'tir), ardından List<String> olarak alındığında ClassCastException ile karşılaşacaktınız. Wildcard varyantı, yazma işlemlerini tamamen yasaklayarak tür güvenliğini korur.
Varargs yöntemi çağrısı, çağrı noktasında neden sessizce genel bir dizi üretir, ve neden @SafeVarargs yalnızca yığın kirlenmesi riskini maskelemekle kalır, çözmez?
void process(T... items) belirlediğinizde, derleyici, argümanları tutmak için bir T[] dizisi üretir; bu, silinme sonrasında aslında bir Object[] haline gelir. @SafeVarargs açıklaması, derleyici uyarısını bastırır, ancak bayt kodunu değiştirmez; yöntem yine bir Object[] alır ki bu da T[] gibi davranır. Tehlike sürer: eğer yöntem items dizisini bir alana depolar veya dışarıya sızmasına izin verirse ve o dizi T olmayan öğeleri içeriyorsa (çağrı noktasındaki yığın kirlenmesi nedeniyle mümkün), sonraki okumalar ClassCastException tetikler. Gerçek güvenlik, items'ı bir ArrayList<T> içine savunmalı bir şekilde kopyalamak ya da yöntem gövdesinde Array.newInstance kullanmayı gerektirir.
Genel dizilerle Arrays.copyOf ya da System.arraycopy kullanırken neden bir ClassCastException oluşabilir, kaynak ve hedef türler görünüşte uyumlu olsa bile, ve Class.getComponentType() nasıl bir çözüm sağlar?
Arrays.copyOf içsel olarak, orijinal dizinin çalışma zamanı sınıfı ile Array.newInstance kullanır. Eğer Object[]'den güvenli bir tür atamasıyla oluşturulmuş bir T[]'niz varsa, bileşen türü Object değil, T olacaktır. Arrays.copyOf(original, newLength) ile kopyaladığınızda, elde ettiğiniz şey bir Object[] olacak ve bu da T[]'ye dönüştürülemez; bu hemen ClassCastException hatasını fırlatır. Çözüm, Class<T> token'ını ayrı bir şekilde takip etmek ve dizinin kendi sınıf nesnesine güvenmek yerine Array.newInstance(componentType, length) çağrısı yapmaktır; bu, yeni dizinin silinmiş uygulaması yerine hedeflenen genel türle eşleşmesini sağlar.