Java jenerik tipi silme: ne zaman ve ne olur?


238

Oracle'ın web sitesinde Java'nın tür silinmesini okudum .

Tip silme ne zaman gerçekleşir? Derleme zamanında mı çalışma zamanında mı? Sınıf ne zaman yüklenir? Sınıf ne zaman somutlaştırılır?

Birçok site (yukarıda belirtilen resmi öğretici dahil) tür silme derleme zamanında gerçekleştiğini söylüyor. Tür bilgileri derleme zamanında tamamen kaldırılırsa, tür bilgisi veya yanlış tür bilgisi olmadan jenerik kullanan bir yöntem çağrıldığında JDK tür uyumluluğunu nasıl denetler?

Aşağıdaki örneği düşünün: Say sınıfının Abir yöntemi var empty(Box<? extends Number> b). A.javaSınıf dosyasını derliyor ve alıyoruz A.class.

public class A {
    public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}

Şu anda başka bir sınıf oluşturmak Byöntemi çağırır emptyolmayan bir parametreli bağımsız değişken (ham tip): empty(new Box()). Biz derlemek B.javaile A.classsınıf yolunda, Javac bir uyarı yükseltmek için akıllı yeterlidir. Yani A.class sahiptir buna depolanan bazı tür bilgileri.

public class B {
    public static void invoke() {
        // java: unchecked method invocation:
        //  method empty in class A is applied to given types
        //  required: Box<? extends java.lang.Number>
        //  found:    Box
        // java: unchecked conversion
        //  required: Box<? extends java.lang.Number>
        //  found:    Box
        A.empty(new Box());
    }
}

Benim tahminim, sınıf yüklendiğinde tür silme gerçekleşir, ancak bu sadece bir tahmin. Peki ne zaman oluyor?



@afryingpan: Cevabımda bahsedilen makale, tip silme işleminin nasıl ve ne zaman gerçekleştiğini ayrıntılı olarak açıklıyor. Ayrıca tür bilgilerinin ne zaman tutulacağını da açıklar. Başka bir deyişle: yaygın jenerikler yaygın inancın aksine Java'da mevcuttur. Bakınız: rgomes.info/using-typetokens-to-retrieve-generic-parameters
Richard Gomes

Yanıtlar:


240

Tip silme jeneriklerin kullanımı için geçerlidir . Orada bir yöntemi / tip olsun veya olmasın demek kesinlikle meta sınıf dosyasında var olan jenerik ve kısıtlamalar vb vardır Ama jenerik zaman neyi kullanılan , bunlar derleme zamanı kontrol ve yürütme zamanı atmalarını dönüştürülür ediyoruz. Yani bu kod:

List<String> list = new ArrayList<String>();
list.add("Hi");
String x = list.get(0);

içine derlendi

List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);

Yürütme sırasında T=String, liste nesnesi için bunu öğrenmenin bir yolu yoktur - bu bilgi kayboldu.

... ancak List<T>arayüz hala kendisini jenerik olarak tanıtıyor.

DÜZENLEME: Sadece açıklığa kavuşturmak için, derleyici değişken olan a ile ilgili bilgileri tutar List<String>- ama yine de bunu T=Stringliste nesnesinin kendisi için bulamazsınız .


6
Hayır, genel bir tür kullanımında bile çalışma zamanında meta veriler mevcut olabilir. Yerel değişkene Reflection üzerinden erişilemez, ancak "List <String> l" olarak bildirilen bir yöntem parametresi için çalışma zamanında Reflection API'sı aracılığıyla kullanılabilen meta veriler türü olacaktır. Evet, "tip silme" pek çok insanın düşündüğü kadar basit değil ...
Rogério

4
@Rogerio: Yorumunuza cevap verdiğim gibi, bir değişkenin türünü elde etmek ile bir nesnenin türünü elde etmek arasında kafanız karıştığına inanıyorum . Alan tanımlasa bile, nesnenin kendisi tür argümanını bilmez.
Jon Skeet

Tabii ki, sadece nesnenin kendisine bakarak bunun bir Liste <Dizleme> olduğunu bilemezsiniz. Ancak nesneler sadece hiçbir yerden ortaya çıkmaz. Yerel olarak oluşturulur, bir yöntem çağırma bağımsız değişkeni olarak iletilir, bir yöntem çağrısından dönüş değeri olarak döndürülür veya bir nesnenin alanından okunur ... Tüm bu durumlarda, çalışma zamanında genel türün ne olduğunu bilirsiniz, ya dolaylı olarak veya Java Reflection API'sini kullanarak.
Rogério

13
@ Rogerio: Nesnenin nereden geldiğini nereden biliyorsun? Bir tür parametreniz List<? extends InputStream>varsa, oluşturulduğunda ne tür olduğunu nasıl bilebilirsiniz? Hatta eğer yapabilirsiniz referans saklanmış alanına yapmanız neden olmalıdır türünü öğrenmek? Nesne hakkındaki tüm diğer bilgileri neden yürütme sırasında elde edebilirsiniz, ancak genel tür argümanlarını alamıyorsunuz? Tip silmeyi geliştiricileri gerçekten etkilemeyen bu küçük şey olarak yapmaya çalışıyorsunuz - oysa bazı durumlarda çok önemli bir sorun olduğunu düşünüyorum .
Jon Skeet

Ancak tür silme, geliştiricileri gerçekten etkilemeyen küçük bir şeydir! Tabii ki, başkaları için konuşamam, ama benim deneyimimde bu hiç önemli değildi. Aslında benim Java alaycı API (JMockit) tasarımında çalışma zamanı türü bilgileri yararlanmak; ironik bir şekilde, .NET alay API'leri C # 'de mevcut genel tip sisteminden daha az yararlanıyor gibi görünüyor.
Rogério

99

Derleyici Generics'i derleme zamanında anlamaktan sorumludur. Derleyici aynı zamanda tip silme dediğimiz bir süreçte genel sınıfların bu "anlayışını" atmaktan da sorumludur . Her şey derleme zamanında gerçekleşir.

Not: Java geliştiricilerinin çoğunun inançlarının aksine, derleme zamanı türü bilgilerini tutmak ve bu bilgileri çok kısıtlı bir şekilde olsa da çalışma zamanında almak mümkündür. Başka bir deyişle: Java, tanınmış jenerikleri çok kısıtlı bir şekilde sağlar .

Tip silme ile ilgili

Derleme zamanında, derleyicinin tam tür bilgisine sahip olduğuna, ancak bayt kodu üretildiğinde bu türün genel olarak tür silme olarak bilinen bir işlemde kasıtlı olarak bırakıldığına dikkat edin . . Bu, uyumluluk sorunları nedeniyle bu şekilde yapılır: Dil tasarımcılarının amacı, platformun sürümleri arasında tam kaynak kodu uyumluluğu ve tam bayt kodu uyumluluğu sağlamaktı. Farklı uygulanmışsa, platformun daha yeni sürümlerine geçerken eski uygulamalarınızı yeniden derlemeniz gerekir. Yapılma şekli, tüm yöntem imzaları korunur (kaynak kodu uyumluluğu) ve hiçbir şeyi yeniden derlemenize gerek yoktur (ikili uyumluluk).

Java'da genelleştirilmiş jeneriklerle ilgili

Derleme zamanı tür bilgilerini tutmanız gerekiyorsa, anonim sınıflar kullanmanız gerekir. Mesele şu ki: anonim sınıfların çok özel bir durumunda, çalışma zamanında tam derleme zamanı türü bilgilerini almak mümkündür, bu başka bir deyişle: birleşik jenerikler anlamına gelir. Bu, anonim sınıflar söz konusu olduğunda derleyicinin tür bilgilerini atmadığı anlamına gelir; bu bilgiler oluşturulan ikili kodda tutulur ve çalışma zamanı sistemi bu bilgileri almanızı sağlar.

Bu konuyla ilgili bir makale yazdım:

https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/

Yukarıdaki makalede açıklanan teknik hakkında bir not, tekniğin geliştiricilerin çoğunluğu için belirsiz olmasıdır. İyi çalışmasına ve iyi çalışmasına rağmen, çoğu geliştirici teknikle karışık veya rahatsız hissediyor. Paylaşılan bir kod tabanınız varsa veya kodunuzu herkese açıklamayı planlıyorsanız, yukarıdaki tekniği önermiyorum. Öte yandan, kodunuzun tek kullanıcısıysanız, bu tekniğin size sağladığı güçten yararlanabilirsiniz.

Basit kod

Yukarıdaki makalede örnek kod bağlantıları bulunmaktadır.


1
@ will824: Cevabı büyük ölçüde geliştirdim ve bazı test senaryolarına bir bağlantı ekledim. Şerefe :)
Richard Gomes

1
Aslında, hem ikili hem de kaynak uyumluluğunu sürdürmediler: oracle.com/technetwork/java/javase/compatibility-137462.html Niyetleri hakkında nereden daha fazla bilgi edinebilirim? Dokümanlar, tür silme kullandığını söylüyor, ancak nedenini söylemiyor.
Dzmitry Lazerka

@Richard Gerçekten, mükemmel bir makale! Yerel sınıfların da çalıştığını ve her iki durumda da (anonim ve yerel sınıflar) istenen tür bağımsız değişkeni ile ilgili bilgilerin yalnızca new Box<String>() {};dolaylı erişim durumunda değil , doğrudan erişim durumunda tutulduğunu ekleyebilir, void foo(T) {...new Box<T>() {};...}çünkü derleyici tür bilgilerini tutmaz. ekteki yöntem bildirimi.
Yann-Gaël Guéhéneuc

Makaleme bozuk bir bağlantı düzelttim. Yavaş yavaş googumu kaldırıyorum ve verilerimi geri kazanıyorum. :-)
Richard Gomes

33

Genel türde bir alanınız varsa, tür parametreleri sınıfa derlenir.

Genel bir tür alan veya döndüren bir yönteminiz varsa, bu tür parametreleri sınıfa derlenir.

Bu bilgiler derleyici bir geçemez söylemek kullandığı şeydir Box<String>etmek empty(Box<T extends Number>)yöntemle.

API karmaşık, ancak böyle yöntemlerle yansıma API yoluyla bu tür bilgileri inceleyebilir getGenericParameterTypes, getGenericReturnType, alanlar için, ve getGenericType.

Genel bir tür kullanan bir kodunuz varsa, derleyici türleri denetlemek için gerektiğinde (arayanda) dökümler ekler. Genel nesnelerin kendileri sadece ham türdür; parametrelenen tip "silinir". Yani, bir oluşturduğunuzda , nesnede sınıf new Box<Integer>()hakkında bilgi yoktur .IntegerBox

Angelika Langer SSS , Java Generics için gördüğüm en iyi referans.


2
Aslında, sınıfa derlenen alanların ve yöntemlerin biçimsel jenerik türüdür, yani "T" yazın. Genel türün gerçek türünü elde etmek için "anonim sınıf hilesi" kullanmanız gerekir .
Yann-Gaël Guéhéneuc

13

Java dilinde jenerikler bu konuda gerçekten iyi bir rehberdir.

Jenerikler, Java derleyicisi tarafından silme adı verilen bir ön uç dönüştürme olarak uygulanır. Bunu (neredeyse) bir kaynaktan-kaynağa çeviri olarak düşünebilirsiniz, böylece jenerik versiyonu jenerik olmayan versiyona loophole()dönüştürülür.

Yani derleme zamanı. JVM, hangisini ArrayListkullandığınızı asla bilemez .

Ayrıca Bay Skeet'in Java'daki jenerikte silme kavramı nedir?


6

Tür silme derleme zamanında gerçekleşir. Silme türünün anlamı, her türü değil genel türü unutmasıdır. Ayrıca, genel türler hakkında hala meta veriler olacaktır. Örneğin

Box<String> b = new Box<String>();
String x = b.getDefault();

dönüştürüldü

Box b = new Box();
String x = (String) b.getDefault();

derleme zamanında. Derleyicinin hangi türün jenerik olduğunu bildiği için değil, aksine, yeterince bilmediği için tip güvenliğini garanti edemeyeceği için uyarı alabilirsiniz.

Ayrıca, derleyici yansıma yoluyla alabileceğiniz bir yöntem çağrısındaki parametrelerle ilgili tür bilgilerini saklar.

Bu kılavuz , konuyla ilgili bulduğum en iyisidir.


6

"Tip silme" terimi, Java'nın jeneriklerle ilgili probleminin doğru tanımı değildir. Tip silme aslında kötü bir şey değildir, aslında performans için çok gereklidir ve genellikle C ++, Haskell, D gibi çeşitli dillerde kullanılır.

Eğer iğrendiriyorsunuz önce gelen tip silinmeye doğru tanımı hatırlama lütfen Wiki

Tip silme nedir?

tür silme, çalışma zamanında yürütülmeden önce, açık tür ek açıklamalarının bir programdan kaldırıldığı yükleme zamanı işlemini ifade eder.

Tür silme, ikili koddaki derlenmiş program herhangi bir tür etiketi içermeyecek şekilde, tasarım zamanında oluşturulan tür etiketlerini veya derleme zamanında çıkarılan tür etiketlerini atmak anlamına gelir. Ve bu, çalışma zamanı etiketlerine ihtiyaç duyduğunuz bazı durumlar dışında, ikili kodla derlenen her programlama dili için geçerlidir. Bu istisnalar, örneğin tüm varoluş türlerini (alt türlere sahip Java Referans Türleri, Birçok dilde Herhangi Bir Tür, Birlik Türleri) içerir. Tür silme işleminin nedeni, programların türler yalnızca soyutlama olduğu ve değerleri ve bunları işlemek için uygun semantikler olduğu için bir tür tek tipli bir dile (yalnızca bitlere izin veren ikili dil) dönüştürülmesidir.

Dolayısıyla bu normal bir doğal şey.

Java'nın sorunu farklıdır ve nasıl anlaşıldığına bağlıdır.

Java hakkında sık sık yapılan jeneriklere sahip olmayan ifadeler de yanlıştır.

Java geriye doğru uyumluluk nedeniyle yanlış yorum yapar.

Yeniden yapılanma nedir?

Bizim itibaren Wiki

Yeniden birleşme, bir bilgisayar programı hakkında soyut bir fikrin, açık bir veri modeline veya bir programlama dilinde yaratılmış başka bir nesneye dönüştürülmesi işlemidir.

Yeniden birleştirme, soyut bir şeyi (Parametrik Tip) uzmanlaşarak somut bir şeye (Beton Türü) dönüştürmek anlamına gelir.

Bunu basit bir örnekle açıklıyoruz:

Tanımlı bir ArrayList:

ArrayList<T>
{
    T[] elems;
    ...//methods
}

bir soyutlamadır, ayrıntılı olarak, somut bir tiple uzmanlaştığında "birleşik" olan bir tür kurucusudur, Tamsayı deyin:

ArrayList<Integer>
{
    Integer[] elems;
}

nerede ArrayList<Integer>bir tür gerçekten.

Ama bu tam olarak Java'nın yapmadığı şey !!! bunun yerine sürekli soyut türleri sınırlarıyla ilişkilendirirler, yani uzmanlaşma için geçirilen parametrelerden bağımsız olarak aynı beton türünü üretirler:

ArrayList
{
    Object[] elems;
}

burada örtülü bağlı Object ( ArrayList<T extends Object>== ArrayList<T>) ile ilişkilendirilir.

Genel dizileri kullanılamaz hale getirmesine ve ham türler için bazı garip hatalara neden olmasına rağmen:

List<String> l= List.<String>of("h","s");
List lRaw=l
l.add(new Object())
String s=l.get(2) //Cast Exception

birçok belirsizliğe neden olur.

void function(ArrayList<Integer> list){}
void function(ArrayList<Float> list){}
void function(ArrayList<String> list){}

aynı işleve bakın:

void function(ArrayList list)

bu nedenle genel yöntem aşırı yüklemesi Java'da kullanılamaz.


2

Android'de tür silme ile karşılaştım. Üretimde küçültme seçeneği ile kepçe kullanıyoruz. Küçültmeden sonra ölümcül istisnam var. Nesnemin kalıtım zincirini göstermek için basit bir işlev yaptım:

public static void printSuperclasses(Class clazz) {
    Type superClass = clazz.getGenericSuperclass();

    Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
    Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));

    while (superClass != null && clazz != null) {
        clazz = clazz.getSuperclass();
        superClass = clazz.getGenericSuperclass();

        Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
        Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));
    }
}

Ve bu işlevin iki sonucu vardır:

Küçültülmüş kod:

D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: com.example.App.SortedListWrapper<com.example.App.Models.User>

D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: android.support.v7.util.SortedList$Callback<T>

D/Reflection: this class: android.support.v7.util.SortedList$Callback
D/Reflection: superClass: class java.lang.Object

D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null

Küçültülmüş kod:

D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: class com.example.App.SortedListWrapper

D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: class android.support.v7.g.e

D/Reflection: this class: android.support.v7.g.e
D/Reflection: superClass: class java.lang.Object

D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null

Bu nedenle, küçültülmüş kodda, gerçek parametreli sınıflar, herhangi bir tür bilgisi olmadan ham sınıf türleriyle değiştirilir. Projem için bir çözüm olarak tüm yansıma çağrılarını kaldırdım ve bunları işlev argümanlarında geçirilen açık param tipleri ile yanıtladım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.