Ara sıra jeneriklerde Java'nın doğru anlamadığını duydum. (en yakın referans burada )
Deneyimsizliğim için kusura bakmayın, ama onları daha iyi yapan ne olabilirdi?
Ara sıra jeneriklerde Java'nın doğru anlamadığını duydum. (en yakın referans burada )
Deneyimsizliğim için kusura bakmayın, ama onları daha iyi yapan ne olabilirdi?
Yanıtlar:
Kötü:
List<byte>
gerçekten byte[]
örneğin a tarafından desteklenir ve kutulama gerekmez)İyi:
En büyük sorun, Java jeneriklerinin yalnızca derleme zamanı olması ve bunu çalışma zamanında tersine çevirebilmenizdir. C #, daha fazla çalışma zamanı denetimi yaptığı için övülür. Bu yazıda gerçekten iyi bir tartışma var ve diğer tartışmalarla bağlantılı.
Class
nesneleri etrafından geçirebilir .
Temel sorun, Java'nın çalışma zamanında aslında jeneriklere sahip olmamasıdır. Bu bir derleme zamanı özelliğidir.
Java'da genel bir sınıf oluşturduğunuzda, sınıftan tüm genel türleri gerçekten kaldırmak ve esasen bunları Nesne ile değiştirmek için "Tür Silme" adlı bir yöntem kullanırlar. Jeneriklerin mil yüksekliğindeki versiyonu, derleyicinin metot gövdesinde her göründüğünde sadece belirli genel tipe dökümleri eklemesidir.
Bunun birçok dezavantajı var. En büyüklerinden biri olan IMHO, genel bir türü incelemek için yansımayı kullanamamanızdır. Tipler bayt kodunda aslında genel değildir ve bu nedenle jenerik olarak incelenemez.
Buradaki farklılıklara harika bir genel bakış: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) çok garip davranışlara yol açar. Aklıma gelen en iyi örnek. varsayalım:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
sonra iki değişken tanımlayın:
MyClass<T> m1 = ...
MyClass m2 = ...
Şimdi ara getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
İkincisinin genel tür bağımsız değişkeni derleyici tarafından çıkarılır çünkü parametreli türle hiçbir ilgisi olmamasına rağmen ham bir türdür (parametreleştirilmiş tür sağlanmamıştır) .
Ayrıca JDK'dan en sevdiğim açıklamadan da bahsedeceğim:
public class Enum<T extends Enum<T>>
Joker karakter kullanımı dışında (karışık bir çanta) .Net jeneriklerinin daha iyi olduğunu düşünüyorum.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
ilk bakışta tuhaf / gereksiz görünebilir ama aslında oldukça ilginçtir, en azından Java'nın / jeneriklerinin kısıtlamaları dahilinde. Numaralandırmalar values()
, öğelerinin bir dizisini numaralandırma olarak değil Enum
, tiplenmiş olarak veren statik bir yönteme sahiptir ve bu tür, istediğiniz anlamına gelen genel parametre tarafından belirlenir Enum<T>
. Elbette, bu yazım yalnızca numaralandırılmış bir tür bağlamında anlamlıdır ve tüm numaralandırmalar alt sınıflarıdır Enum
, bu yüzden istiyorsunuz Enum<T extends Enum>
. Ancak Java, ham türleri jeneriklerle karıştırmayı sevmez, dolayısıyla Enum<T extends Enum<T>>
tutarlılık sağlar.
Gerçekten tartışmalı bir fikir ortaya atacağım. Jenerikler dili karmaşıklaştırır ve kodu karmaşıklaştırır. Örneğin, bir dizeyi dizeler listesiyle eşleyen bir haritam olduğunu varsayalım. Eski günlerde bunu basitçe şöyle ilan edebilirdim
Map someMap;
Şimdi bunu ilan etmeliyim
Map<String, List<String>> someMap;
Ve bunu bir yönteme her geçirdiğimde, o büyük uzun bildirimi baştan tekrar etmem gerekiyor. Kanımca, tüm bu fazladan yazım geliştiricinin dikkatini dağıtıyor ve onu "bölgenin" dışına çıkarıyor. Ayrıca, kod çok fazla kusurla dolu olduğunda, bazen ona daha sonra geri dönmek ve önemli mantığı bulmak için tüm yarıkları hızla gözden geçirmek zordur.
Java zaten yaygın olarak kullanılan en ayrıntılı dillerden biri olarak kötü bir üne sahip ve jenerikler bu soruna katkıda bulunuyor.
Ve tüm bu ekstra ayrıntılar için gerçekten ne satın alıyorsunuz? Birisi bir Tamsayıyı Dizeleri tutması gereken bir koleksiyona koyduğunda veya bir Tamsayı koleksiyonundan bir Dize çıkarmaya çalıştığında gerçekten kaç kez sorun yaşadınız? Ticari Java uygulamaları oluşturmada 10 yıllık deneyimimde, bu hiçbir zaman büyük bir hata kaynağı olmadı. Yani, fazladan ayrıntı için ne elde ettiğinden emin değilim. Bu gerçekten bana ekstra bürokratik bagaj olarak geliyor.
Şimdi gerçekten tartışmalı olacağım. Java 1.4'teki koleksiyonlarla ilgili en büyük sorun olarak gördüğüm şey, her yerde yazım zorunluluğu. Ben bu tip yayınlarını, jeneriklerle aynı sorunların çoğunu içeren ekstra, ayrıntılı kabalık olarak görüyorum. Yani, örneğin, sadece yapamam
List someList = someMap.get("some key");
Yapmak zorundayım
List someList = (List) someMap.get("some key");
Elbette bunun nedeni, get () 'in List'in bir süper türü olan bir Nesne döndürmesidir. Yani bir typecast olmadan atama yapılamaz. Yine, bu kuralın sizi gerçekten ne kadar satın aldığını düşünün. Tecrübelerime göre pek değil.
Bence Java, 1) jenerik eklemeseydi, ancak 2) bunun yerine bir süper tipten bir alt türe örtük çevrim yapılmasına izin verseydi çok daha iyi olurdu. Çalışma zamanında yanlış yayınların yakalanmasına izin verin. O zaman tanımlama basitliğine sahip olabilirdim
Map someMap;
ve sonra yapıyor
List someList = someMap.get("some key");
tüm kusurlar ortadan kalkacaktı ve koduma büyük bir yeni hata kaynağı ekleyeceğimi gerçekten düşünmüyorum.
Derleme zamanı olmasının ve çalışma zamanı olmamasının bir başka yan etkisi de, genel türün yapıcısını çağıramamanızdır. Yani onları genel bir fabrika uygulamak için kullanamazsınız ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Java jenerikleri, derleme zamanında doğruluk açısından kontrol edilir ve daha sonra tüm tür bilgileri kaldırılır (işleme tür silme adı verilir . Böylece, jenerik, genel olmayan ham türüneList<Integer>
indirgenecektir.List
, rastgele sınıf nesnelerini içerebilen, .
Bu, çalışma zamanında listeye rastgele nesneler ekleyebilmenin yanı sıra, hangi türlerin genel parametreler olarak kullanıldığını söylemek artık imkansızdır. İkincisi sırayla sonuçlanır
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Keşke bu bir wiki olsaydı, böylece başkalarına da ekleyebilseydim ... ama ...
sorunlar:
<
?>
[] 'ı , ancak buna izin verilmiyor)Tüm tür silme karmaşasını göz ardı ederek, belirtilen jenerikler işe yaramıyor.
Bu derler:
List<Integer> x = Collections.emptyList();
Ancak bu bir sözdizimi hatasıdır:
foo(Collections.emptyList());
Foo şu şekilde tanımlanır:
void foo(List<Integer> x) { /* method body not important */ }
Dolayısıyla, bir ifade türünün kontrol edilip edilmeyeceği, yerel bir değişkene mi yoksa bir yöntem çağrısının gerçek parametresine mi atandığına bağlıdır. Bu ne kadar çılgınca?
Java'ya jeneriklerin tanıtılması zor bir görevdi çünkü mimarlar işlevselliği, kullanım kolaylığını ve eski kodla geriye dönük uyumluluğu dengelemeye çalışıyorlardı. Beklendiği gibi taviz verilmesi gerekiyordu.
Java'nın jenerik uygulamasının, dilin karmaşıklığını kabul edilemez bir düzeye çıkardığını düşünenler de var (Ken Arnold'un " Zararlı Olarak Kabul Edilen Jenerikler " e bakınız). Angelika Langer'in Generics SSS'leri , işlerin ne kadar karmaşık olabileceği konusunda oldukça iyi bir fikir veriyor.
Java, Generics'i çalışma zamanında zorlamaz, yalnızca derleme zamanında zorlar.
Bu, genel Koleksiyonlara yanlış türleri eklemek gibi ilginç şeyler yapabileceğiniz anlamına gelir.
Java jenerikleri yalnızca derleme zamanıdır ve jenerik olmayan koda derlenir. C # 'da, gerçek derlenmiş MSIL geneldir. Bunun performans açısından çok büyük etkileri vardır çünkü Java hala çalışma sırasında yayın yapmaktadır. Daha fazlası için buraya bakın .
Java Posse # 279 - Joe Darcy ve Alex Buckley ile röportaj dinlerseniz , bu konu hakkında konuşurlar. Bu aynı zamanda, Reified Generics for Java başlıklı Neal Gafter blog gönderisine de bağlantı veriyor :
Java'da jeneriklerin uygulanma şeklinin neden olduğu kısıtlamalardan pek çok kişi memnun değil. Spesifik olarak, genel tip parametrelerinin somutlaştırılmamasından mutsuzdurlar: çalışma zamanında kullanılamazlar. Jenerikler, genel tip parametrelerinin çalışma zamanında basitçe kaldırıldığı silme kullanılarak uygulanır.
Bu blog yazısı, gereksinimlerdeki geçiş uyumluluğu ile ilgili noktayı vurgulayan daha eski bir giriş olan Silme Yoluyla Şaşırtıcı: cevap bölümüne atıfta bulunuyor .
Amaç, hem kaynak hem de nesne kodunun geriye dönük uyumluluğunu ve ayrıca geçiş uyumluluğunu sağlamaktı.