Yanıtlar:
Temel olarak jeneriklerin derleyici hilesi ile Java'da uygulanma şekli. Derlenmiş jenerik kod aslında sadece kullandığı java.lang.Object
hakkında konuşmak her yerde T
(veya başka tip parametresi) - ve gerçekten genel bir türü olduğunu derleyici anlatmak için bazı meta var.
Genel bir türe veya yönteme göre bazı kodlar derlediğinizde, derleyici gerçekten ne demek istediğinizi (yani tür argümanının ne T
olduğunu) hesaplar ve doğru şeyi yaptığınızı derlerken doğrular , ancak yayılan kod tekrar konuşur açısından java.lang.Object
- derleyici gerektiğinde ekstra dökümler üretir. Yürütme zamanında, a List<String>
ve a List<Date>
tam olarak aynıdır; ekstra tip bilgisi derleyici tarafından silinmiştir .
Bunu, örneğin, yürütme zamanında tutulan C # ile karşılaştırın; kodun typeof(T)
eşdeğeri gibi ifadeleri içermesine izin verir T.class
- ikincisinin geçersiz olması dışında. (.NET jenerikleri ve Java jenerikleri arasında daha fazla farklılık vardır, unutmayın) Tip silme, Java jenerikleri ile uğraşırken birçok "tek" uyarı / hata mesajının kaynağıdır.
Diğer kaynaklar:
Object
(zayıf yazılan bir senaryoda) olarak List<String>
sunulan bir şeyin aslında a ) olup olmadığını öğrenmek son derece kolaydır . Java'da bu mümkün değil - bunun ArrayList
orijinal jenerik türünün ne olduğunu değil, olduğunu öğrenebilirsiniz. Bu tür şeyler, örneğin serileştirme / serileştirme durumlarında ortaya çıkabilir. Başka bir örnek, bir kabın genel türünün örneklerini oluşturabilmesi gerektiğidir - bu türü Java'da (as Class<T>
) ayrı olarak geçirmeniz gerekir .
Class<T>
Sadece Java bu bilgileri tutmaz çünkü bir yapıcı (veya genel yöntem) bir parametre eklemek zorunda olduğum çeşitli yerler vardır . EnumSet.allOf
Örneğin bak - yöntemin genel tür argümanı yeterli olmalıdır; neden "normal" bir argüman belirtmem gerekiyor? Cevap: silme yazın. Bu tür şeyler API'yı kirletir. İlgi alanı dışında, .NET jeneriklerini çok mu kullandınız? (devam ediyor)
Bir yan not gibi, derleyicinin silme gerçekleştirdiğinde ne yaptığını görmek ilginç bir alıştırmadır - tüm konsepti kavramak biraz daha kolay hale getirir. Derleyiciyi, jenerikleri silip döktüğünüz java dosyalarını çıktılamak için iletebileceğiniz özel bir bayrak vardır. Bir örnek:
javac -XD-printflat -d output_dir SomeFile.java
-printflat
Dosyaları oluşturur derleyici devredilmiştir alır bayrağıdır. ( -XD
Kısmen javac
aslında sadece derleme yapar çalıştırılabilir kavanoz el teslim söyler javac
, ama ben kazmak ...) -d output_dir
Gerekli çünkü derleyici yeni .java dosyaları koymak için bir yere ihtiyacı var.
Bu, elbette, silmekten daha fazlasını yapar; derleyicinin yaptığı tüm otomatik işler burada yapılır. Örneğin, varsayılan kurucular da eklenir, yeni foreach tarzı for
döngüler düzenli for
döngülere genişletilir , vb. Otomatik olarak gerçekleşen küçük şeyleri görmek güzel.
Silme, kelimenin tam anlamıyla kaynak kodda bulunan tür bilgilerinin derlenmiş bayt kodundan silindiği anlamına gelir. Bunu bir kodla anlayalım.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericsErasure {
public static void main(String args[]) {
List<String> list = new ArrayList<String>();
list.add("Hello");
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String s = iter.next();
System.out.println(s);
}
}
}
Bu kodu derler ve daha sonra bir Java decompiler ile kodalarsanız, böyle bir şey alırsınız. Ayrıştırılan kodun, orijinal kaynak kodunda bulunan tür bilgisi izi içermediğine dikkat edin.
import java.io.PrintStream;
import java.util.*;
public class GenericsErasure
{
public GenericsErasure()
{
}
public static void main(String args[])
{
List list = new ArrayList();
list.add("Hello");
String s;
for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s))
s = (String)iter.next();
}
}
jigawot
dedim, işe yarıyor.
Zaten çok eksiksiz Jon Skeet'in cevabını tamamlamak için, tür silme kavramının önceki Java sürümleriyle uyumluluk ihtiyacından kaynaklandığını fark etmelisiniz . .
Başlangıçta EclipseCon 2007'de sunuldu (artık mevcut değil), uyumluluk şu noktaları içeriyordu:
Orijinal cevap:
Dolayısıyla:
new ArrayList<String>() => new ArrayList()
Daha büyük bir yeniden yapılanma için önermeler vardır . Dil yapılarının sadece sözdizimsel şeker değil, kavramlar olması gereken "Soyut bir kavramı gerçek olarak algıla" olduğunu anlayın.
Ayrıca checkCollection
, belirtilen koleksiyonun dinamik olarak türden güvenli bir görünümünü döndüren Java 6 yönteminden de bahsetmeliyim . Yanlış türde bir eleman eklemek için yapılan herhangi bir girişim anında sonuç verir ClassCastException
.
Dildeki jenerikler mekanizması derleme zamanı (statik) tip denetimi sağlar, ancak bu mekanizmayı kontrol edilmemiş dökümlerle yenmek mümkündür .
Derleyici, bu tür tüm denetlenmeyen işlemlerle ilgili uyarılar verdiğinden, bu genellikle bir sorun oluşturmaz.
Bununla birlikte, statik tip kontrolünün tek başına yeterli olmadığı zamanlar vardır, örneğin:
ClassCastException
yanlış yazılmış bir öğenin parametreli bir koleksiyona yerleştirildiğini belirten bir a ile başarısız olur . Ne yazık ki, istisna hatalı eleman yerleştirildikten sonra herhangi bir zamanda ortaya çıkabilir, bu nedenle sorunun gerçek kaynağı hakkında genellikle çok az bilgi sağlar veya hiç bilgi vermez.Neredeyse dört yıl sonra Temmuz 2012'yi güncelleyin:
Şimdi (2012) " API Geçiş Uyumluluk Kuralları (İmza Testi) " bölümünde ayrıntılı olarak açıklanmıştır
Java programlama dili, türlerle ilgili bazı yardımcı bilgiler dışında, eski ve genel sürümlerin genellikle aynı sınıf dosyaları oluşturmasını sağlayan silme özelliğini kullanarak jenerikleri uygular. Herhangi bir istemci kodunu değiştirmeden veya yeniden derlemeden eski bir sınıf dosyasını genel bir sınıf dosyasıyla değiştirmek mümkün olduğundan ikili uyumluluk bozulmaz.
Genel olmayan eski kodla arayüz oluşturmayı kolaylaştırmak için, parametreli bir türün silinmesini bir tür olarak kullanmak da mümkündür. Böyle bir türe ham tür denir ( Java Dil Belirtimi 3 / 4.8 ). Ham türe izin vermek, kaynak kodu için geriye dönük uyumluluk da sağlar.
Buna göre,
java.util.Iterator
sınıfın aşağıdaki sürümleri hem ikili hem de kaynak kodu geriye dönük olarak uyumludur:
Class java.util.Iterator as it is defined in Java SE version 1.4:
public interface Iterator {
boolean hasNext();
Object next();
void remove();
}
Class java.util.Iterator as it is defined in Java SE version 5.0:
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
Zaten tamamlanmış olan Jon Skeet cevabını tamamlıyor ...
Silme yoluyla jenerik ilaçların uygulanmasının bazı can sıkıcı sınırlamalara yol açtığı belirtilmiştir (örn. Hayır new T[42]
). Ayrıca, bu şekilde bir şey yapmanın birincil nedeninin bayt kodunda geriye dönük uyumluluk olduğu da belirtilmiştir. Bu da (çoğunlukla) doğrudur. Oluşturulan bytecode -target 1.5, sadece şekersiz döküm -target 1.4'ten biraz farklıdır. Teknik olarak, (büyük hile ile) çalışma zamanında genel tür örneklemelerine erişim elde etmek bile mümkündür (bayt koduyla), bayt kodunda gerçekten bir şey olduğunu kanıtlamak .
Daha ilginç olan nokta ise (silinmemiş olan) silinmeyi kullanarak jenerik ilaçların uygulanmasının, yüksek seviyeli tip sisteminin başarabileceği şeylerde biraz daha esneklik sağlamasıdır. Bunun iyi bir örneği, Scala'nın CLR'ye karşı JVM uygulamasıdır. JVM'de, JVM'nin kendisinin jenerik tipler üzerinde herhangi bir kısıtlama getirmemesi nedeniyle daha yüksek türlerin doğrudan uygulanması mümkündür (çünkü bu "tipler" etkili bir şekilde mevcut değildir). Bu, parametre örneklemeleri hakkında çalışma zamanı bilgisine sahip olan CLR ile çelişir. Bu nedenle CLR'nin kendisi, jeneriklerin nasıl kullanılması gerektiğine dair bazı kavramlara sahip olmalı ve sistemi beklenmedik kurallarla genişletmeye çalışmalıdır. Sonuç olarak, Scala'nın CLR üzerindeki daha yüksek türleri derleyicinin kendisinde öykünen garip bir silme biçimi kullanılarak uygulanır,
Silme, çalışma zamanında yaramaz şeyler yapmak istediğinizde rahatsız edici olabilir, ancak derleyici yazarlarına en fazla esnekliği sunar. Sanırım bu, yakında hiç gitmemesinin bir parçası.
Anladığım kadarıyla ( .NET adamı olarak) JVM jenerik kavramı yoktur, bu nedenle derleyici tip parametrelerini Object ile değiştirir ve sizin için tüm dökümleri gerçekleştirir.
Bu, Java jeneriklerinin sözdizimi şekerinden başka bir şey olmadığı ve referansla iletildiğinde boks / kutudan çıkarma gerektiren değer türleri için herhangi bir performans iyileştirmesi sunmadığı anlamına gelir.
İyi açıklamalar var. Sadece tür silme işleminin bir kod çözücü ile nasıl çalıştığını göstermek için bir örnek ekliyorum.
Orijinal sınıf,
import java.util.ArrayList;
import java.util.List;
public class S<T> {
T obj;
S(T o) {
obj = o;
}
T getob() {
return obj;
}
public static void main(String args[]) {
List<String> list = new ArrayList<>();
list.add("Hello");
// for-each
for(String s : list) {
String temp = s;
System.out.println(temp);
}
// stream
list.forEach(System.out::println);
}
}
Kodun bayt kodundan ayrıştırılması,
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Consumer;
public class S {
Object obj;
S(Object var1) {
this.obj = var1;
}
Object getob() {
return this.obj;
}
public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
var1.add("Hello");
// for-each
Iterator iterator = var1.iterator();
while (iterator.hasNext()) {
String string;
String string2 = string = (String)iterator.next();
System.out.println(string2);
}
// stream
PrintStream printStream = System.out;
Objects.requireNonNull(printStream);
var1.forEach(printStream::println);
}
}
Neden Generices kullanılır?
Özetle, jenerikler sınıfları, arayüzleri ve yöntemleri tanımlarken tiplerin (sınıflar ve arayüzler) parametre olmasını sağlar. Yöntem bildirimlerinde kullanılan daha bilinen biçimsel parametreler gibi, tip parametreleri de aynı kodu farklı girişlerle yeniden kullanmanız için bir yol sağlar. Fark, biçimsel parametrelere girişlerin değerler, tip parametrelerine girişlerin ise tipler olmasıdır. jenerik kullanılan ode jenerik olmayan kod üzerinde birçok faydası vardır:
Tip Silme Nedir
Derleme zamanında daha sıkı tip kontroller sağlamak ve genel programlamayı desteklemek için Java diline jenerikler tanıtıldı. Jenerikleri uygulamak için Java derleyicisi aşağıdakilere tür silme uygular:
[NB] -Köprü yöntemi nedir? Kısaca, gibi parametreli bir arayüz durumunda Comparable<T>
, bu derleyici tarafından ek yöntemlerin eklenmesine neden olabilir; bu ek yöntemlere köprüler denir.
Silme Nasıl Çalışır?
Bir türün silinmesi şu şekilde tanımlanır: tüm tür parametrelerini parametrelenmiş türlerden atın ve herhangi bir tür değişkenini sınırının silinmesiyle veya herhangi bir sınırı yoksa Object ile veya varsa en soldaki sınırın silinmesiyle değiştirin. çoklu sınırlar. İşte bazı örnekler:
List<Integer>
, List<String>
ve List<List<String>>
olduğunu List
.List<Integer>[]
DİR List[]
.List
Herhangi bir ham tip için benzer şekilde silinme kendisidir.Integer
tip parametreleri olmayan herhangi bir tip için benzerdir.T
tanımındaki asList
IS Object
, çünkü T
herhangi bir bağlandığı.T
tanımındaki max
IS Comparable
, çünkü T
bağlanmış olanComparable<? super T>
.T
nihai tanımı max
ise Object
, çünkü
T
bağlı olan Object
ve Comparable<T>
ve biz bağlı en soldaki silinmesini alır.Jenerik kullanırken dikkatli olmanız gerekir
Java'da iki farklı yöntem aynı imzayı kullanamaz. Jenerikler silme ile uygulandığından, iki farklı yöntemin aynı silme ile imzaları olamaz. Bir sınıf, imzaları aynı silme özelliğine sahip iki yöntemi aşırı yükleyemez ve bir sınıf aynı silme özelliğine sahip iki arabirimi uygulayamaz.
class Overloaded2 {
// compile-time error, cannot overload two methods with same erasure
public static boolean allZero(List<Integer> ints) {
for (int i : ints) if (i != 0) return false;
return true;
}
public static boolean allZero(List<String> strings) {
for (String s : strings) if (s.length() != 0) return false;
return true;
}
}
Bu kodun aşağıdaki gibi çalışmasını istiyoruz:
assert allZero(Arrays.asList(0,0,0));
assert allZero(Arrays.asList("","",""));
Bununla birlikte, bu durumda, her iki yöntemin imzalarının silinmesi aynıdır:
boolean allZero(List)
Bu nedenle, derleme zamanında bir ad çakışması bildirilir. Her iki yönteme aynı adı vermek ve aşırı yükleyerek aralarında ayrım yapmaya çalışmak mümkün değildir, çünkü silindikten sonra bir yöntem çağrısını diğerinden ayırt etmek imkansızdır.
Umarım, Reader keyfine varacaksınız :)