Diziler neden kovaryant, fakat jenerikler değişmez?


160

Joshua Bloch'un Etkili Java'sından,

  1. Diziler, genel türden iki önemli şekilde farklıdır. İlk diziler kovaryanttır. Jenerikler değişmezdir.
  2. Kovaryant, basitçe, X, Y'nin alt tipi ise, X [] 'nin de Y [] alt tipi olacağı anlamına gelir. Diziler kovaryanttır Dize Object So alt türü olduğu için

    String[] is subtype of Object[]

    Değişmez, X'in Y'nin alt türü olmasına bakılmaksızın,

     List<X> will not be subType of List<Y>.

Benim sorum neden dizileri Java'da kovaryant yapma kararı? Arrays neden değişmez, ancak Listeler kovaryant gibi başka SO gönderileri de vardır. , ancak Scala'ya odaklanmış gibi görünüyorlar ve takip edemiyorum.


1
Bu jenerikler daha sonra eklendiği için değil mi?
Sotirios Delimanolis

1
diziler ve koleksiyonlar arasında karşılaştırma haksız olduğunu düşünüyorum, Koleksiyonlar arka planda diziler kullanın !!
Ahmed Adel İsmail

4
@ EL-conteDe-monteTereBentikh Tüm koleksiyonlar örneğin LinkedList.
Paul Bellora

@PaulBellora Haritalar'ın Koleksiyon uygulayıcılarından farklı olduğunu biliyorum, ancak SCPJ6'da Koleksiyonların genellikle dizilere dayandığını okudum!
Ahmed Adel İsmail

Çünkü ArrayStoreException yok; Koleksiyona dizi olarak sahip olduğu yerde yanlış öğe eklerken. Yani Collection bunu sadece alım zamanında ve dökümden dolayı bulabilir. Böylece jenerikler bu sorunun çözülmesini sağlayacaktır.
Kanagavelu Sugumar

Yanıtlar:


150

Via wikipedia :

Java ve C # 'ın ilk sürümlerinde jenerik (diğer bir deyişle parametrik polimorfizm) yoktu.

Böyle bir ortamda, dizileri değişmez yapmak yararlı polimorfik programları dışlar. Örneğin, bir diziyi karıştırmak için bir işlev veya Object.equalsöğelerdeki yöntemi kullanarak iki diziyi eşitlik açısından test eden bir işlev yazmayı düşünün . Uygulama, dizide saklanan öğenin tam türüne bağlı değildir, bu nedenle tüm diziler üzerinde çalışan tek bir işlev yazmak mümkün olmalıdır. Tür fonksiyonlarını uygulamak kolaydır

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

Ancak, dizi türleri değişmez olarak ele alınırsa, bu işlevleri yalnızca tam olarak bir dizi üzerinde çağırmak mümkün olacaktır Object[]. Örneğin, bir dizge karıştırılamaz.

Bu nedenle, hem Java hem de C # dizi türlerini kovaryant olarak ele alır. Örneğin, C # ' string[]da bir alt tiptir object[]ve Java'da String[]bir alt tiptir Object[].

Bu "Neden daha doğru "? Diziler covariant Neden" sorusunu yanıtlar ya vardı covariant yapılan diziler anda ?"

Jenerikler tanıtıldığında, Jon Skeet'in bu cevabında belirtilen nedenlerden dolayı kasıtlı olarak kovaryant edilmediler :

Hayır, a List<Dog>bir değil List<Animal>. A ile neler yapabileceğinizi düşünün List<Animal>- ona herhangi bir hayvan ekleyebilirsiniz ... bir kedi dahil. Şimdi, mantıksal olarak yavru çöpüne bir kedi ekleyebilir misin? Kesinlikle hayır.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

Aniden çok karışık bir kediniz var.

Wikipedia makalesinde açıklanan dizileri kovaryant yapmak için orijinal motivasyon jenerikler için geçerli değildi, çünkü joker karakterler kovaryans (ve kontravaryans) ifadesini mümkün kıldı, örneğin:

boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);

3
evet. örneğin:Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";
eagertoLearn

21
Bu doğru. Dizilerin yeniden kullanılabilir türleri vardır ve ArrayStoreExceptiongerektiğinde atarlar . Açıkçası, o zaman değerli bir uzlaşma olarak kabul edildi. Bugünün aksine: çoğu, dizi kovaryansını geriye dönük olarak bir hata olarak görür.
Paul Bellora

1
Neden "birçok" bunu bir hata olarak görüyor? Dizi kovaryansına sahip olmaktan çok daha yararlıdır. Ne sıklıkla bir ArrayStoreException gördünüz; oldukça nadirdirler. Buradaki ironi affedilemez imo ... Java'da şimdiye kadar yapılan en kötü hatalar arasında kullanım sitesi varyansı, yani joker karakterler var.
Scott

3
@ScottMcKinney: "Neden" birçok "bunu bir hata olarak görüyor?" AIUI, bunun nedeni, dizi kovaryansının tüm dizi atama işlemlerinde dinamik tür testleri gerektirmesidir (derleyici optimizasyonları belki de yardımcı olabilir mi?) Bu da önemli bir çalışma zamanı yüküne neden olabilir.
Dominique Devriese

Teşekkürler Dominique, ama benim gözlemimden, "birçoğunun" bir hatanın daha çok birkaç kişinin söylediklerini papağan çizgisi boyunca görmesinin sebebi görünüyor. Yine, dizi kovaryansına yeni bir bakış atmak, zarar vermekten çok daha yararlıdır. Yine, Java'nın yaptığı gerçek BIG hatası, joker karakterler aracılığıyla kullanım sitesi genel varyansı idi. Bu "birçok" itiraf etmek istiyorum sandığından daha fazla soruna neden oldu.
Scott

30

Bunun nedeni, her dizinin çalışma sırasında öğe türünü bilmesidir; genel koleksiyon ise tür silme nedeniyle değildir.

Örneğin:

String[] strings = new String[2];
Object[] objects = strings;  // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime

Genel koleksiyonlarda buna izin verildiyse:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;  // let's say it is valid
objects.add(12);  // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this

Ancak bu, daha sonra birisi listeye erişmeye çalıştığında sorunlara neden olur:

String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String

Sanırım Paul Bellora'nın cevabı, NEDEN Diziler kovaryant hakkında yorum yaptığı için daha uygun. Diziler değişmez hale getirildiyse, o zaman para cezası. onunla tip silmeniz olurdu. Erasure özelliğinin temel nedeni geriye dönük uyumluluk için doğru mu?
eagertoLearn

@ user2708477, evet, geriye dönük uyumluluk nedeniyle silme türü tanıtıldı. Ve evet, cevabım, jeneriklerin neden değişmez olduğu başlıklı soruya cevap vermeye çalışıyor.
Katona

Dizilerin türlerini bildiği gerçeği, kovaryans kodun bir şeyi sığmayacağı bir dizide saklamasını istemesine izin verirken - böyle bir mağazanın gerçekleşmesine izin verileceği anlamına gelmez. Sonuç olarak, dizilerin kovaryant olmasıyla ortaya çıkan tehlike seviyesi, türlerini bilmeselerdi olduğundan çok daha azdır.
supercat

@supercat, doğru, belirtmek istediğim, yerinde silme özelliği olan jenerikler için, kovaryansın çalışma zamanı kontrollerinin asgari güvenliği ile uygulanamaması
Katona

1
Şahsen bu cevabın, Koleksiyonlar olamazken Dizilerin neden kovaryant olduğuna dair doğru bir açıklama sağladığını düşünüyorum. Teşekkürler!
20'de asgs

22

Olabilir bu yardım: -

Jenerikler kovaryant değildir

Java dilindeki diziler kovaryanttır - yani, Integer Sayı'yı genişletirse (ki), o zaman sadece bir Tamsayı da Sayı değildir, aynı zamanda bir Tamsayı [] Number[]da a'dır ve bir Integer[]burada a Number[]denir. (Sayı tamsayı bir üst tip ise daha resmen, daha sonra Number[]bir üst tip olduğunu Integer[].) Aynı zamanda jenerik türleri için de geçerlidir düşünebilir - List<Number>bir üst tip olduğunu List<Integer>ve bir geçirebilmesi List<Integer>bir yerde List<Number>bekleniyor. Ne yazık ki, bu şekilde çalışmıyor.

Bu şekilde çalışmamasının iyi bir nedeni var: Güvenlik jeneriklerinin sağlaması gereken türden kopacaktı. Eğer bir atayabilir düşünün List<Integer>bir için List<Number>. Daha sonra aşağıdaki kodu bir içine bir tamsayı olmayan bir şeyi koymak için izin verecek List<Integer>:

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

Ln bir olduğu için List<Number>bir Float eklemek son derece yasal görünüyor. Ama eğer ln ile takma isimlendirilmişse li, li tanımında örtük olan tip güvenliği vaadini kıracaktır - bu tamsayıların bir listesidir, bu yüzden jenerik tipler kovaryant olamaz.


3
Diziler ArrayStoreExceptioniçin çalışma zamanında bir.
Sotirios Delimanolis

4
sorum şu WHY: Diziler kovaryant. Sotirios'un belirttiği gibi, Arrays ile ArrayStoreException çalışma zamanında elde edilirdi, Arrays değişmez hale getirildiyse, derleme zamanında bu hatayı tespit edebilir miyiz?
eagertoLearn

@eagertoLearn: Java Önemli bir anlamsal zayıflık onun tipi sisteminde hiçbir şey "başka bir şey türevlerini tutan Diziyi ayırt olmasıdır Animalama hiçbir şey içermelidir Array" den başka bir yerden alınan herhangi bir öğe kabul etmek zorunda olmadığı," Animal, ve dışarıdan sağlanan referansları kabul etmeye istekli olmalıdır Animal. İlkine ihtiyaç duyan Catkod bir diziyi kabul etmelidir , ancak ikincisine ihtiyaç duyan kod kabul etmemelidir Derleyici iki türü ayırt edebiliyorsa, derleme zamanı denetimi sağlayabilir. Ne yazık ki, onları ayıran tek şey ...
Supercat

... kodun kendilerine bir şey depolamaya çalışıp çalışmadığı ve çalışma zamanına kadar bunu bilmenin bir yolu yok.
Supercat

3

Diziler en az iki nedenden ötürü kovaryanttır:

  • Asla kovaryant olmayacak bilgileri barındıran koleksiyonlar için kullanışlıdır. T koleksiyonunun kovaryant olması için, destek deposu da kovaryant olmalıdır. Bir tanesi destek deposu olarak kullanılmayan (örneğin bir ağaç veya bağlantılı liste kullanarak) değişmez bir Tkoleksiyon tasarlayabilse de T[], bu tür bir koleksiyonun bir dizi tarafından desteklenen bir performans sergilemesinin yanı sıra mümkün olmayacaktır. Kovaryant değişmez koleksiyonlar sağlamanın daha iyi bir yolunun, bir destek deposu kullanabilecekleri bir "kovaryant değişmez dizi" tipini tanımlamak olabileceğini, ancak sadece dizi kovaryansına izin vermenin daha kolay olacağını iddia edebiliriz.

  • Diziler sık ​​sık, içinde ne tür bir şey olacağını bilmeyen bir kodla mutasyona uğrayacak, ancak diziye aynı diziden okunmayan hiçbir şeyi koymayacaktır. Bunun en iyi örneği sıralama kodu. Kavramsal olarak dizi türlerinin öğeleri takas etme veya bunlara izin verme yöntemlerini içermesi (bu tür yöntemler herhangi bir dizi türüne eşit olarak uygulanabilir) veya bir diziye ve bir veya daha fazla şeye referans tutan bir "dizi manipülatör" nesnesi tanımlaması mümkün olabilir okunmuş ve önceden okunmuş öğeleri geldikleri diziye depolamak için yöntemler içerebilir. Diziler kovaryant olmasaydı, kullanıcı kodu böyle bir türü tanımlayamazdı, ancak çalışma zamanı bazı özel yöntemler içerebilirdi.

Dizilerin kovaryant olması çirkin bir hack olarak görülebilir, ancak çoğu durumda çalışma kodunun oluşturulmasını kolaylaştırır.


1
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.- İyi bir nokta
eagertoLearn

3

Parametrik tiplerin önemli bir özelliği, polimorfik algoritmalar, yani parametre değerinden bağımsız olarak bir veri yapısı üzerinde çalışan algoritmalar yazma yeteneğidir Arrays.sort().

Jenerikler ile, bu joker karakterler ile yapılır:

<E extends Comparable<E>> void sort(E[]);

Gerçekten kullanışlı olması için, joker karakter türleri joker karakter yakalama gerektirir ve bu da bir tür parametresi kavramını gerektirir. Java'ya diziler eklendiğinde bunların hiçbiri mevcut değildi ve referans türü kovaryant yapım dizileri, polimorfik algoritmalara izin vermek için çok daha basit bir yola izin verdi:

void sort(Comparable[]);

Ancak, bu basitlik statik tip sistemde bir boşluk açtı:

String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException

bir başvuru türü dizisine her yazma erişiminin çalışma zamanı denetimini gerektirir.

Özetle, jenerikler tarafından şekillendirilen daha yeni yaklaşım, tür sistemini daha karmaşık, ancak aynı zamanda daha statik tipte güvenli kılarken, daha eski yaklaşım daha basit ve daha az statik olarak tür güvenli. Dil tasarımcıları daha basit bir yaklaşımı tercih ettiler, tür sisteminde nadiren sorunlara neden olan küçük bir boşluk açmaktan daha önemli şeyler yaptılar. Daha sonra, Java kurulduğunda ve acil ihtiyaçlar karşılandığında, jenerikler için doğru yapmak için kaynaklara sahiptiler (ancak diziler için değiştirmek mevcut Java programlarını bozmuş olurdu).


2

Jenerikler değişmezdir : JSL 4.10'dan :

... Alt tipleme genel tipler boyunca uzanmaz: T <: U, C<T><: C<U>...

ve birkaç satır daha ileride, JLS ayrıca
Dizilerin kovaryant olduğunu (ilk madde işareti) açıklar :

4.10.3 Dizi Türleri Arasındaki Alt Tip

resim açıklamasını buraya girin


2

Sanırım diziyi kovaryant yapan ilk etapta yanlış bir karar verdiler. O anlatıldığı gibi tip güvenliğini kırar burada ve onlar yüzünden geriye dönük uyumluluk ve jenerik için aynı hatayı yapmamaya çalıştık bundan sonra buna takıldı. Bu, Joshua Bloch'un "Etkili Java (ikinci baskı)" kitabının 25. Maddesinde dizileri listelemeyi tercih etmesinin nedenlerinden biri.


Josh Block, Java'nın koleksiyon çerçevesinin (1.2) ve Java'nın jeneriklerinin (1.5) yazarıydı. Yani herkesin şikayet ettiği jenerikleri yapan adam tesadüfen kitabı yazmanın en iyi yol olduğunu söyleyen adam mı? Büyük bir sürpriz değil!
cpurdy

1

Benim almam: Kod A [] dizisini beklerken ve B [] 'yi verirseniz, burada B A'nın bir alt sınıfıdır, endişelenmeniz gereken sadece iki şey vardır: bir dizi öğesini okuduğunuzda ne olur ve yazarsanız ne olur? o. Bu nedenle, tip güvenliğinin her durumda korunmasını sağlamak için dil kuralları yazmak zor değildir (ana kural, ArrayStoreExceptionA'yı B'ye yapıştırmaya çalıştığınızda a'nın atılabilmesidir []). Bir jenerik için, bir sınıf ilan ettiğinizde , sınıfın vücudunda SomeClass<T>herhangi bir sayıda yol Tkullanılabilir ve sanırım ne zaman ilgili kurallar yazmak için tüm olası kombinasyonları çalışmanın çok karmaşık olduğunu tahmin ediyorum. her şeye izin verilir ve izin verilmez.

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.