Neden bazıları Java'nın jenerik uygulamasının kötü olduğunu iddia ediyor?


115

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?


23
Bunu kapatmak için bir neden göremiyorum; jenerikler hakkında etrafta dolaşan tüm saçmalıklarla, Java ve C # arasındaki farklar arasında bir miktar açıklığa sahip olmak, insanların yanlış bilgilendirilmiş çamurdan kaçmaya çalışanlarına yardımcı olabilir.
Rob

Yanıtlar:


146

Kötü:

  • Tür bilgileri derleme sırasında kaybolur, bu nedenle yürütme sırasında "ne tür" olması gerektiğini söyleyemezsiniz
  • Değer türleri için kullanılamaz (bu önemli bir şeydir - .NET'te a List<byte>gerçekten byte[]örneğin a tarafından desteklenir ve kutulama gerekmez)
  • Genel yöntemleri çağırmak için sözdizimi berbat (IMO)
  • Kısıtlamalar için sözdizimi kafa karıştırıcı olabilir
  • Joker karakter kullanımı genellikle kafa karıştırıcıdır
  • Yukarıdakilerden dolayı çeşitli kısıtlamalar - döküm vb.

İyi:

  • Joker karakter, kovaryans / kontravansın arama tarafında belirtilmesine izin verir, bu da birçok durumda çok düzgündür
  • Hiç yoktan iyidir!

51
@Paul: Sanırım geçici bir kırılmayı tercih ederdim ama sonrasında iyi bir API. Veya .NET yolunu kullanın ve yeni koleksiyon türlerini tanıtın. (2.0'daki .NET jenerikleri 1.1 uygulamaları
bozmadı

6
Var olan büyük bir kod tabanıyla, onu arayan herkesi değiştirmeden jenerik kullanmak için bir yöntemin imzasını değiştirmeye başlayabileceğim gerçeğini seviyorum.
Paul Tomblin

38
Ancak bunun dezavantajları çok büyük ve kalıcı. Kısa vadeli büyük bir kazanç ve uzun vadeli bir IMO kaybı var.
Jon Skeet

11
Jon - "Hiç yoktan iyidir" özneldir :)
MetroidFan2002

6
@Rogerio: İlk "Kötü" öğemin doğru olmadığını iddia ettin. İlk "Kötü" öğem, bilgilerin derleme sırasında kaybolduğunu söylüyor. Bu yanlış olduğu için, söz konusu bilgilerin demem gerek olmadığı derleme sırasında kaybolan ... gibi diğer geliştiriciler kafa karıştırıcı için, sen ne dediğimi tarafından kurcalayan tek tek görünmektedir.
Jon Skeet

26

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


Bu gerçekten bir sorun değil, asla çalışma zamanı için yapılmadı. Sanki dağlara tırmanamadıkları için teknelerin sorun olduğunu söylüyorsunuz. Java, dil olarak birçok insanın ihtiyaç duyduğu şeyi yapar: türleri anlamlı bir şekilde ifade eder. Çalışma zamanı türü bilgileri için, insanlar yine de Classnesneleri etrafından geçirebilir .
Vincent Cantin

1
O haklı. anolojiniz yanlış çünkü tekneyi tasarlayanlar onu dağlara tırmanmak için tasarlamış olmalıdır. Tasarım hedefleri, ihtiyaç duyulan şey için çok iyi düşünülmemişti. Şimdi hepimiz sadece bir tekneyle sıkışıp kaldık ve dağlara tırmanamayız.
WiegleyJ

1
@VincentCantin - kesinlikle bir sorun. Dolayısıyla hepimiz bundan şikayet ediyoruz. Java jenerikleri yarı pişmiş.
Josh M.

17

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


2
Neden reddedildiğinden emin değilim. Anladığım kadarıyla haklısın ve bu bağlantı çok bilgilendirici. Yukarı-oy kullandı.
Jason Jackson

@ Jason, teşekkürler. Orijinal versiyonumda bir yazım hatası vardı, sanırım bunun için oy vermedim.
JaredPar

3
Hata, sen çünkü edebilirsiniz genel bir tipi, genel bir yöntem imzası bakmak için yansıma kullanın. Parametrelendirilmiş bir örnekle ilişkili genel bilgileri incelemek için yansımayı kullanamazsınız. Örneğin, foo yöntemine sahip bir sınıfım varsa (List <String>), "String"
oxbow_lakes,

Tür silme işleminin gerçek tür parametrelerini bulmayı imkansız hale getirdiğini söyleyen birçok makale var. Diyelim ki: Nesne x = yeni X [Y] (); x verildiğinde Y türünü nasıl bulacağını gösterebilir misin?
user48956

@oxbow_lakes - genel bir tür bulmak için yansıma kullanmak zorunda olmak, neredeyse bunu yapamamak kadar kötüdür. Örneğin, bu kötü bir çözüm.
Josh M.

14
  1. Çalışma zamanı uygulaması (yani tür silme değil);
  2. İlkel türleri kullanma yeteneği (bu, (1) ile ilgilidir);
  3. Joker karakter kullanımı yararlı olsa da sözdizimi ve ne zaman kullanılacağını bilmek birçok insanı şaşırtan bir şeydir. ve
  4. Performans artışı yok (çünkü (1); Java jenerikleri, Nesnelerin dökümü için sözdizimsel şekerdir).

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


Ancak derleyici size, özellikle rawtypes lint seçeneğiyle söyleyecektir.
Tom Hawtin - tackline

Pardon, neden son satır bir derleme hatası? Eclipse'de onunla uğraşıyorum ve orada başarısız olmasını sağlayamıyorum - eğer geri kalanı derlemek için yeterince şey eklersem.
oconnor0

public class Redundancy<R extends Redundancy<R>>;)
java.is.for.desktop

@ oconnor0 Bu bir derleme hatası değil, görünürde bir neden yokken bir derleyici uyarısı:The expression of type List needs unchecked conversion to conform to List<String>
artbristol

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

10

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.


15
Üzgünüm, söylediğin hemen her şeye katılmıyorum. Ama iyi savunduğunuz için oy vermeyeceğim.
Paul Tomblin

2
Elbette, cevabımda tam olarak önerdiğim şeyi yapan Python ve Ruby gibi dillerde oluşturulmuş çok sayıda büyük, başarılı sistem örneği var.
Clint Miller

Sanırım bu tür bir fikre karşı tüm itirazım, bunun kötü bir fikir olması değil, Python ve Ruby gibi modern dillerin geliştiriciler için hayatı kolaylaştırması ve kolaylaştırması, geliştiricilerin entelektüel olarak kayıtsız kalması ve sonunda daha düşük olması. kendi kodlarının anlaşılması.
Dan Tao

4
"Ve tüm bu ekstra ayrıntılar için gerçekten ne satın alıyorsunuz? ..." Zavallı bakım programcısına bu koleksiyonlarda ne olması gerektiğini söyler. Bunu ticari Java uygulamalarıyla çalışırken bir sorun olarak buldum.
richj

4
"Birisi [y] s bulundurması gereken bir koleksiyona [x] koyduğunda gerçekten kaç kez sorun yaşadınız?" - Tanrım, sayımı kaybettim! Ayrıca hata olmasa bile bu bir okunabilirlik katilidir. Ve nesnenin ne olacağını bulmak için birçok dosyayı taramak (veya hata ayıklamak) beni gerçekten bölgeden çıkarıyor. Haskell'i sevebilirsiniz - hatta güçlü yazım ama daha az acımasız (çünkü türler çıkarılır).
Martin Capodici

8

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 ++


6

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");

5

Keşke bu bir wiki olsaydı, böylece başkalarına da ekleyebilseydim ... ama ...

sorunlar:

  • Tür Silme (çalışma zamanı kullanılabilirliği yok)
  • İlkel türler için destek yok
  • Ek açıklamalarla uyumsuzluk (her ikisi de 1.5'e eklendi. Ek açıklamaların özellikleri aceleye getirmekten başka neden jeneriklere izin vermediğinden hala emin değilim)
  • Dizilerle uyumsuzluk. (Bazen gerçekten Class gibi bir şey yapmak istiyorum <?> [] 'ı , ancak buna izin verilmiyor)
  • Garip joker sözdizimi ve davranış
  • Java sınıfları arasında jenerik desteğin tutarsız olduğu gerçeği. Koleksiyon yöntemlerinin çoğuna eklediler, ancak arada bir, orada olmadığı bir durumla karşılaşırsınız.

5

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?


3
Javac'ın tutarsız çıkarımı saçmalık.
mP.

3
İkinci formun reddedilmesinin nedeninin, yöntemin aşırı yüklenmesi nedeniyle "foo" nun birden fazla sürümü olabileceğinden kaynaklandığını varsayıyorum.
Jason Creighton

2
Java 8 iyileştirilmiş hedef türü çıkarımını
sunduğundan

4

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.


3

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.


1
Derleyici, bunu başarırsanız, hata yaptığınızı söyleyecektir.
Tom Hawtin - tackline

@Tom - zorunlu olarak değil. Derleyiciyi kandırmanın yolları vardır.
Paul Tomblin

2
Derleyicinin size söylediklerini dinlemiyor musunuz? (ilk
yorumuma

1
@Tom, bir ArrayList <String> 'e sahipseniz ve bunu basit bir Liste alan eski tarz bir yönteme (örneğin, eski veya üçüncü taraf kodu) aktarırsanız, bu yöntem daha sonra bu Listeye istediği her şeyi ekleyebilir. Çalışma zamanında zorlanırsa bir istisna alırsınız.
Paul Tomblin

1
statik void addToList (Liste listesi) {list.add (1); } List <String> list = new ArrayList <String> (); addToList (liste); Bu, çalışma zamanında derlenir ve hatta çalışır. Bir sorun gördüğünüz tek zaman, listeden bir dizge bekleyerek çıkardığınız ve bir int aldığınız zamandır.
Laplie Anderson

3

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 .


2

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

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.