List <Dog>, <Animal> Listesinin bir alt sınıfı mı? Java jenerikleri neden dolaylı olarak polimorfik değildir?


770

Java jeneriklerinin miras / polimorfizmi nasıl ele aldığı konusunda biraz kafam karıştı.

Aşağıdaki hiyerarşiyi varsayalım -

Hayvan (Ebeveyn)

Köpek - Kedi (Çocuk)

Diyelim ki bir yöntemim var doSomething(List<Animal> animals). Kalıtım ve polimorfizm kurallarına herkes tarafından, bir varsayılabilir List<Dog> olan bir List<Animal>ve bir List<Cat> olan bir List<Animal>ve böylece ya bir bu yönteme geçebileceğini -. Öyle değil. Bu davranışı elde etmek istersem, açıkça söyleyerek hayvanın herhangi bir alt sınıfının bir listesini kabul etme yöntemini söylemeliyim doSomething(List<? extends Animal> animals).

Bunun Java'nın davranışı olduğunu anlıyorum. Sorum neden ? Polimorfizm neden genellikle örtüktür, ancak jenerikler söz konusu olduğunda belirtilmelidir?


17
Ve şimdi beni rahatsız eden tamamen ilgisiz bir dilbilgisi sorusu - başlığım "neden Java'nın jenerikleri değil " veya "neden Java'nın jenerikleri değil ?" Tek bir varlık olduğu için "jenerikler" tekil mi yoksa tekil mi?
froadie

25
Java'da yapılan jenerikler parametrik polimorfizmin çok zayıf bir şeklidir. Onlara çok fazla iman etmeyin ( eskiden olduğu gibi), çünkü bir gün acıklı sınırlamalarına sert bir şekilde çarpacaksınız: Cerrah Handable <Scalpel>, Handable <Sponge> KABOOM ! Does not [TM] hesaplamak. Java jenerik sınırlamanız var. Herhangi bir OOA / OOD, Java'ya ince bir şekilde çevrilebilir (ve MI, Java arayüzleri kullanılarak çok güzel bir şekilde yapılabilir), ancak jenerikler bunu kesmez. "Koleksiyonlar" ve prosedürel programlama için iyidir (çoğu Java programcısının zaten yaptığı şey budur ...).
SözdizimiT3rr0r

8
<Dog> Listesinin süper sınıfı Liste <Hayvan> değil, Liste <?> (Yani bilinmeyen türdeki liste). Generics, derlenmiş koddaki tür bilgilerini siler. Bu, jenerikler (java 5 ve üstü) kullanan kodun, java'nın jeneriksiz önceki sürümleriyle uyumlu olması için yapılır.
rai.skumar


9
@froadie hiç kimse yanıt vermiyor gibi görünüyordu ... kesinlikle "neden Java'nın jenerikleri değil ..." olmalıdır. Diğer bir konu, "jenerik" in aslında bir sıfat olması ve bu yüzden "jenerik", "jenerik" tarafından değiştirilmiş bırakılan çoğul bir isme atıfta bulunmasıdır. "Bu işlev geneldir" diyebilirsiniz, ancak bu "bu işlev geneldir" demekten daha zahmetli olur. Ancak, "Java genel özelliklere sahiptir" yerine "Java genel işlevlere ve sınıflara sahiptir" demek biraz zahmetlidir. Sıfatları üzerine yüksek lisans tezi yazan biri olarak, bence çok ilginç bir soru üzerine tökezlediniz!
dantiston

Yanıtlar:


916

Hayır, bir List<Dog>olduğunu değil bir 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 ArrayList<Dog>(); // ArrayList implements List
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.

Şimdi, olamaz bir ekleme Cata List<? extends Animal>bunu bir olduğunu bilmediğim için List<Cat>. Bir değeri alabilir ve bunun bir olacağını bilirsiniz Animal, ancak keyfi hayvanlar ekleyemezsiniz. Tersi doğrudur List<? super Animal>- bu durumda Animalgüvenli bir şekilde bir ekleyebilirsiniz , ancak ondan ne alınabileceği hakkında hiçbir şey bilmiyorsunuz, çünkü bir olabilir List<Object>.


50
İlginçtir ki, köpeklerin her liste olan sezgi söyler gibi, gerçekten de bir hayvan listesi. Mesele şu ki, her hayvan listesi bir köpek listesi değildir, bu nedenle listenin bir kedi ekleyerek mutasyona uğraması problemdir.
Ingo

66
@Ingo: Hayır, gerçekten değil: bir hayvan listesine kedi ekleyebilirsin, ama köpek listesine kedi ekleyemezsin. Köpeklerin bir listesi, sadece salt okunur olarak düşünürseniz hayvanların bir listesidir.
Jon Skeet

12
@JonSkeet - Tabii ki, ama bir kediden yeni bir liste yapmak ve bir köpek listesi yapmak aslında köpek listesini değiştirmeyi kim zorunlu kılıyor? Bu, Java'da keyfi bir uygulama kararıdır. Mantık ve sezgiye aykırı.
Ingo

7
@Ingo: Başlamak için "kesinlikle" kullanmazdım. En üstte "Gitmek isteyebileceğimiz oteller" yazan bir listeniz varsa ve birisi buna bir yüzme havuzu eklediyse, bunun geçerli olduğunu düşünür müsünüz? Hayır - binaların bir listesi olmayan otellerin bir listesi. Ve "Köpeklerin listesi hayvanların listesi değil" bile demiştim gibi değil - Ben kod terimleriyle , bir kod yazı tipine koydum . Gerçekten burada bir belirsizlik olduğunu düşünmüyorum. Alt sınıfı kullanmak yine de yanlış olur - alt sınıflandırma ile değil atama uyumluluğu ile ilgilidir.
Jon Skeet

14
@ruakh: Sorun, daha sonra derleme zamanında engellenebilecek bir şey yürütme zamanı için punting olmasıdır. Ve dizi kovaryansının başlangıçta bir tasarım hatası olduğunu iddia ediyorum.
Jon Skeet

84

Aradığınız şeye kovaryant tip parametreleri denir . Başka bir deyişle, bir yöntemde bir nesne türü diğeriyle ikame edilebiliyorsa (örneğin, Animalbununla değiştirilebilir Dog), aynı şey bu nesneleri kullanan ifadeler için de geçerlidir (bu List<Animal>ile değiştirilebilir List<Dog>). Sorun genel olarak değişken listeler için kovaryansın güvenli olmamasıdır. Varsayalım List<Dog>ve bir olarak kullanılıyor List<Animal>. Buna List<Animal>gerçekten bir Kedi eklemeye çalıştığınızda ne olur List<Dog>? Otomatik olarak tip parametrelerinin kovaryant olmasına izin verilmesi tip sistemini bozar.

Tür parametrelerinin kovaryant olarak belirtilmesine izin vermek için sözdizimi eklemek yararlı olacaktır, bu ? extends Fooyöntem yöntem bildirimlerinden kaçınır , ancak bu ek karmaşıklık ekler.


44

Nedeni List<Dog>bir değil List<Animal>, yani, örneğin, bir ekleyebilirsiniz olan Catbir içine List<Animal>, ama bir içine List<Dog>... sen mümkünse jenerik daha genişletilebilir yapmak için joker karakterleri kullanabilirsiniz; Örneğin, a'dan okuma, a'dan okumaya List<Dog>benzer, List<Animal>ancak yazma değildir.

Java Dilinde Jenerik ve Java Tutorials Jenerik üzerinde Bölüm bazı şeyler neden olarak çok iyi, derinlemesine açıklama var ya polimorfik olmayan veya jenerik ile izin verdi.


36

Sanırım diğer cevapların bahsettiği noktaya eklenmelidir.

List<Dog>Java'da değilList<Animal>

ayrıca doğrudur

Köpeklerin listesi - İngilizce hayvanların bir listesi ( makul bir yorum altında)

Tamamen geçerli olan OP'nin sezgisinin çalışma şekli ikinci cümledir. Ancak, bu sezgiyi uygularsak, kendi tür sisteminde Java esque olmayan bir dil alırız: Diyelim ki dilimiz köpek listemize bir kedi eklemeye izin veriyor. Bu ne anlama geliyor? Bu, listenin köpeklerin bir listesi olmaktan çıktığı ve sadece hayvanların bir listesi olarak kaldığı anlamına gelir. Ve memelilerin bir listesi ve dört ayaklıların bir listesi.

Başka bir deyişle: List<Dog>Java'daki A , İngilizce'deki "köpeklerin listesi" anlamına gelmez, "köpeklerin olabileceği bir liste ve başka hiçbir şey" anlamına gelir.

Daha genel olarak, OP'nin sezgisi, nesneler üzerindeki işlemlerin türlerini değiştirebileceği bir dile ya da bir nesnenin tür (ler) i değerinin (dinamik) bir fonksiyonuna uygundur.


Evet, insan dili daha bulanık. Ama yine de, köpek listesine farklı bir hayvan eklediğinizde, bu hala bir hayvan listesi, ancak artık bir köpek listesi değil. Buradaki fark, bulanık bir mantığa sahip bir insanın, bunu fark etmenin genellikle bir sorunu yoktur.
Vlasec

Dizilerle sürekli karşılaştırma yapan biri daha da kafa karıştırıcı olduğundan, bu cevap benim için çivilendi. Benim sorunum dil sezgisiydi.
FLonLon

32

Generics'in bütün mesele buna izin vermemesi olduğunu söyleyebilirim. Bu tür bir kovaryansa izin veren dizilerdeki durumu düşünün:

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

Bu kod iyi derlenir, ancak java.lang.ArrayStoreException: java.lang.Booleanikinci satırda bir çalışma zamanı hatası atar . Tipik değildir. Generics'in amacı derleme zamanı türü güvenliğini eklemektir, aksi takdirde jeneriksiz düz bir sınıfa sadık kalabilirsiniz.

Şimdi daha esnek olmanız gereken zamanlar var ve bunun için ? super Classve ne ? extends Classiçin. Birincisi, bir türe Collection(örneğin) eklemeniz gerektiğinde ve ikincisi, ondan güvenli bir şekilde okumanız gerektiğinde kullanılır. Ancak her ikisini aynı anda yapmanın tek yolu belirli bir türe sahip olmaktır.


13
Tartışmalı olarak, dizi kovaryansı bir dil tasarım hatasıdır. Tür silme nedeniyle, aynı davranışın genel toplama için teknik olarak imkansız olduğunu unutmayın.
Michael Borgwardt

" Generics'in asıl amacının buna izin vermemesi olduğunu söyleyebilirim. " Asla emin olamazsınız: Java ve Scala'nın Tip Sistemleri Unsound'dur: Boş İşaretçilerin Varoluşsal Krizi (OOPSLA 2016'da sunulmuştur) (düzeltildiği için göründüğü için)
David Tonhofer

15

Sorunu anlamak için dizilerle karşılaştırma yapmak faydalıdır.

List<Dog>olduğu değil alt sınıfı List<Animal>.
Ama Dog[] bir alt sınıfı Animal[].

Diziler yeniden kullanılabilir ve kovaryanttır .
Reifiable , tür bilgilerinin çalışma zamanında tamamen erişilebilir olduğu anlamına gelir.
Bu nedenle diziler çalışma zamanı türü güvenliği sağlar, ancak derleme zamanı türü güvenliği sağlamaz.

    // All compiles but throws ArrayStoreException at runtime at last line
    Dog[] dogs = new Dog[10];
    Animal[] animals = dogs; // compiles
    animals[0] = new Cat(); // throws ArrayStoreException at runtime


Jenerikler için tam tersi: Jenerikler silinir ve değişmezdir .
Bu nedenle jenerikler çalışma zamanı tipi güvenliği sağlayamazlar, ancak derleme zamanı türü güvenliği sağlarlar.
Aşağıdaki kodda, jenerikler kovaryantsa , 3. satırda yığın kirliliği yapmak mümkün olacaktır .

    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = dogs; // compile-time error, otherwise heap pollution
    animals.add(new Cat());

2
Tam da bu nedenle, iddia edilebilir Java Diziler kırık ,
leonbloy

Kovaryant olan diziler bir derleyici "özelliğidir".
Cristik

6

Burada verilen cevaplar beni tam olarak ikna etmedi. Bunun yerine başka bir örnek vereyim.

public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) {
    consumer.accept(supplier.get());
}

kulağa hoş geliyor, değil mi? Ama sadece s için s Consumerve Suppliers iletebilirsiniz Animal. Bir Mammaltüketiciniz var, ancak bir Ducktedarikçiniz varsa, her ikisi de hayvan olmasına rağmen uygun olmamalıdır. Buna izin vermemek için ek kısıtlamalar eklenmiştir.

Yukarıdakilerin yerine, kullandığımız türler arasındaki ilişkileri tanımlamamız gerekir.

Örneğin.,

public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

yalnızca bize tüketici için doğru türde nesne sağlayan bir tedarikçi kullanabilmemizi sağlar.

OTOH, biz de yapabiliriz

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) {
    consumer.accept(supplier.get());
}

diğer tarafa gidiyoruz: türünü tanımlıyor Supplierve içine konabileceğini kısıtlıyoruz Consumer.

Hatta yapabiliriz

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

Sezgisel ilişkileri olan yerlerde, Life-> Animal-> Mammal-> Dog, Catvb, hatta bir koyabilirsiniz Mammalbir içine Lifetüketici değil, bir Stringbir içine Lifetüketici.


1
4 versiyon arasında # 2 muhtemelen yanlış. Örneğin, (Consumer<Runnable>, Supplier<Dog>)süre Dogile Animal & Runnable
arayamayız

5

Bu tür davranışlar için temel mantık Generics, tip silme mekanizmasını takip etmektir . Bu nedenle, çalışma zamanında , böyle bir silme işleminin bulunmadığı durumun collectionaksine türünü tanımlamanızın bir yolu arraysyoktur. Sorunuza geri dönelim ...

Diyelim ki aşağıda verilen bir yöntem var:

add(List<Animal>){
    //You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism
}

Şimdi java, arayanın bu türe hayvan türü listesi eklemesine izin veriyorsa, koleksiyona yanlış bir şey ekleyebilir ve çalışma zamanında da tür silme nedeniyle çalışacaktır. Dizilerde bu tür senaryolar için çalışma zamanı istisnası elde edersiniz ...

Böylece özünde bu davranış, koleksiyona yanlış bir şey ekleyemeyecek şekilde uygulanır. Şimdi tür silme generics olmadan eski java ile uyumluluk vermek için var olduğuna inanıyorum ....


4

Alt tipleme parametreli tipler için değişmezdir . Sınıfın Dogbir alt türü olmasına rağmen Animal, parametrelenen tür List<Dog>bir alt türü değildir List<Animal>. Buna karşılık, kovaryant alt tip diziler tarafından kullanılır, bu nedenle dizi tipi Dog[]bir alt tipidir Animal[].

Değişmez alt tipleme, Java tarafından zorlanan tür kısıtlamalarının ihlal edilmemesini sağlar. @Jon Skeet tarafından verilen aşağıdaki kodu göz önünde bulundurun:

List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);

@Jon Skeet tarafından belirtildiği gibi, bu kod yasadışıdır, çünkü aksi takdirde bir köpek beklendiğinde bir kediyi geri getirerek tür kısıtlamalarını ihlal eder.

Yukarıdakileri diziler için benzer kodla karşılaştırmak öğreticidir.

Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];

Kod yasal. Ancak, bir dizi deposu özel durumu atar . Bir dizi, çalışma zamanında türünü taşır, bu şekilde JVM, kovaryant alt tiplemenin tip güvenliğini zorlayabilir.

Bunu daha iyi anlamak javapiçin aşağıdaki sınıfın oluşturduğu bayt koduna bakalım :

import java.util.ArrayList;
import java.util.List;

public class Demonstration {
    public void normal() {
        List normal = new ArrayList(1);
        normal.add("lorem ipsum");
    }

    public void parameterized() {
        List<String> parameterized = new ArrayList<>(1);
        parameterized.add("lorem ipsum");
    }
}

javap -c DemonstrationBu komutu kullanarak aşağıdaki Java bayt kodunu gösterir:

Compiled from "Demonstration.java"
public class Demonstration {
  public Demonstration();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void normal();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return

  public void parameterized();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return
}

Çevrilmiş yöntem gövdelerinin kodunun aynı olduğunu gözlemleyin. Derleyici parametreli her tipin yerini sildi . Bu özellik, geriye dönük uyumluluğu bozmadığı için çok önemlidir.

Sonuç olarak, çalışma zamanı güvenliği parametreli tipler için mümkün değildir, çünkü derleyici her parametreli tipin yerini silerek alır. Bu, parametreli tiplerin sözdizimsel şekerden başka bir şey olmadığını gösterir.


3

Aslında istediğinizi elde etmek için bir arayüz kullanabilirsiniz.

public interface Animal {
    String getName();
    String getVoice();
}
public class Dog implements Animal{
    @Override 
    String getName(){return "Dog";}
    @Override
    String getVoice(){return "woof!";}

}

Daha sonra koleksiyonları kullanarak

List <Animal> animalGroup = new ArrayList<Animal>();
animalGroup.add(new Dog());

1

Liste öğelerinin verilen süper türün alt sınıfları olduğundan eminseniz, bu yaklaşımı kullanarak listeyi yayınlayabilirsiniz:

(List<Animal>) (List<?>) dogs

Listeyi bir kurucuya aktarmak veya üzerinde yineleme yapmak istediğinizde bu yararlıdır


2
Bu aslında çözdüğünden daha fazla sorun yaratacaktır
Ferrybig

Listeye bir kedi eklemeye çalışırsanız, sorun yaratacağından emin olun, ancak döngü amaçları için onun sadece ayrıntılı olmayan cevap olduğunu düşünüyorum.
sagits

1

cevap yanı sıra diğer cevaplar doğrudur. Bu cevaplara yardımcı olacağını düşündüğüm bir çözümle ekleyeceğim. Bence bu programlamada sıkça ortaya çıkıyor. Dikkat edilmesi gereken bir nokta, Koleksiyonlar (Listeler, Kümeler, vb.) İçin ana sorunun Koleksiyona eklemesidir. Burada işler bozulur. Çıkarmak bile sorun değil.

Çoğu durumda, biz kullanabilirsiniz Collection<? extends T>ziyade Collection<T>ve bu ilk seçenek olmalıdır. Ancak, bunu yapmanın kolay olmadığı durumlar buluyorum. Bunun her zaman yapılacak en iyi şey olup olmadığı tartışmalıdır. Burada bir sınıf DownCastCollection dönüştürmek alabilir bir Collection<? extends T>sunuyorum Collection<T>(biz benzer sınıfları tanımlayabilirsiniz, Set, NavigableSet, ..) standart yaklaşım kullanırken çok rahatsız edici kullanmak için. Aşağıda nasıl kullanılacağına dair bir örnek ( Collection<? extends Object>bu durumda da kullanabiliriz , ancak DownCastCollection kullanarak göstermeyi basit tutuyorum.

/**Could use Collection<? extends Object> and that is the better choice. 
* But I am doing this to illustrate how to use DownCastCollection. **/

public static void print(Collection<Object> col){  
    for(Object obj : col){
    System.out.println(obj);
    }
}
public static void main(String[] args){
  ArrayList<String> list = new ArrayList<>();
  list.addAll(Arrays.asList("a","b","c"));
  print(new DownCastCollection<Object>(list));
}

Şimdi sınıf:

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> {
private Collection<? extends E> delegate;

public DownCastCollection(Collection<? extends E> delegate) {
    super();
    this.delegate = delegate;
}

@Override
public int size() {
    return delegate ==null ? 0 : delegate.size();
}

@Override
public boolean isEmpty() {
    return delegate==null || delegate.isEmpty();
}

@Override
public boolean contains(Object o) {
    if(isEmpty()) return false;
    return delegate.contains(o);
}
private class MyIterator implements Iterator<E>{
    Iterator<? extends E> delegateIterator;

    protected MyIterator() {
        super();
        this.delegateIterator = delegate == null ? null :delegate.iterator();
    }

    @Override
    public boolean hasNext() {
        return delegateIterator != null && delegateIterator.hasNext();
    }

    @Override
    public  E next() {
        if(!hasNext()) throw new NoSuchElementException("The iterator is empty");
        return delegateIterator.next();
    }

    @Override
    public void remove() {
        delegateIterator.remove();

    }

}
@Override
public Iterator<E> iterator() {
    return new MyIterator();
}



@Override
public boolean add(E e) {
    throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object o) {
    if(delegate == null) return false;
    return delegate.remove(o);
}

@Override
public boolean containsAll(Collection<?> c) {
    if(delegate==null) return false;
    return delegate.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}

@Override
public boolean removeAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.removeAll(c);
}

@Override
public boolean retainAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.retainAll(c);
}

@Override
public void clear() {
    if(delegate == null) return;
        delegate.clear();

}

}


Bu iyi bir fikir, o kadar çok ki Java SE'de zaten var. ; )Collections.unmodifiableCollection
Radiodef

1
Doğru ama tanımladığım koleksiyon değiştirilebilir.
dan b

Evet, değiştirilebilir. Collection<? extends E>yine de, tür güvenli olmayan bir şekilde kullanmadığınız sürece (örneğin, başka bir şeye dönüştürmek gibi) bu davranışı doğru şekilde işler. Gördüğüm tek avantaj, addoperasyonu çağırdığınızda, döküm yapsanız bile bir istisna atıyor.
Vlasec

0

JavaSE öğreticisinden örnek alalım

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

Öyleyse neden bir köpek listesi (daireler) dolaylı olarak hayvanların (şekiller) bir listesi olarak düşünülmemelidir:

// drawAll method call
drawAll(circleList);


public void drawAll(List<Shape> shapes) {
   shapes.add(new Rectangle());    
}

Java "mimarlarının" bu sorunu çözen 2 seçeneği vardı:

  1. bir alt türün örtük olarak süper tür olduğunu düşünmeyin ve şimdi olduğu gibi bir derleme hatası verin

  2. alt türü süpertip olarak düşünün ve "add" yöntemini derlerken kısıtlayın (böylece, bir daire listesi, şekil alt türü geçilecekse, derleyici bunu algılamalı ve derleme hatasıyla sizi kısıtlamalıdır. ) o.

Açık nedenlerden ötürü, bu ilk yolu seçti.


0

Ayrıca, derleyicinin genel sınıfları nasıl tehdit ettiğini de dikkate almalıyız: "genel" ifadelerinde, genel argümanları her doldurduğumuzda farklı bir tür oluşturur.

Böylece elimizdeki ListOfAnimal, ListOfDog, ListOfCatvb, son yukarıya biz jenerik argümanlar belirtmek derleyici tarafından "yaratılan" olma o ayrı sınıflar olan. Ve bu düz bir hiyerarşi (aslında ilgili Listbir hiyerarşi değil).

Genel sınıflar söz konusu olduğunda kovaryansın neden anlam ifade etmediği bir diğer argüman, temelde tüm sınıfların aynı olduğu - Listörneklerdir. A Listgenel argümanı doldurarak uzmanlaşmak sınıfı genişletmez, sadece bu belirli argüman için çalışmasını sağlar.


0

Sorun iyi tanımlanmıştır. Ancak bir çözüm var; yapmak doSomething jenerik:

<T extends Animal> void doSomething<List<T> animals) {
}

şimdi doSomething'i List <Dog> veya List <Cat> veya List <Animal> ile çağırabilirsiniz.


0

başka bir çözüm yeni bir liste oluşturmak

List<Dog> dogs = new ArrayList<Dog>(); 
List<Animal> animals = new ArrayList<Animal>(dogs);
animals.add(new Cat());

0

Bu örnek kodu kullanan Jon Skeet'in yanıtı:

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

En derin düzeyde, buradaki sorun şudur dogsve animalsbir referans paylaşmaktadır. Bu, bu işi yapmanın bir yolunun, referans eşitliğini bozacak tüm listeyi kopyalamak olduğu anlamına gelir:

// This code is fine
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog());
List<Animal> animals = new ArrayList<>(dogs); // Copy list
animals.add(new Cat());
Dog dog = dogs.get(0);   // This is fine now, because it does not return the Cat

Çağırdıktan sonra List<Animal> animals = new ArrayList<>(dogs);, sonradan doğrudan atanamıyor animalsbirine dogsya cats:

// These are both illegal
dogs = animals;
cats = animals;

bu nedenle yanlış alt türünü Animallisteye koyamazsınız , çünkü yanlış alt tür yoktur - herhangi bir alt tür nesnesi ? extends Animaleklenebilir animals.

Açıkçası, bu semantiği değiştirir, çünkü listeler animalsve dogsartık paylaşılmaz, bu nedenle bir listeye eklemek diğerine Cateklenmez (bu tam olarak istediğiniz şeydir), yalnızca bir listeye eklenebilecek sorunu önlemek için Dognesneler içermesi gerekiyordu ). Ayrıca, listenin tamamını kopyalamak verimsiz olabilir. Ancak bu, referans eşitliğini bozarak tip denkliği problemini çözer.

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.