Elenco <Cane> è una sottoclasse di Elenco <Animale>? Perché i generici Java non sono implicitamente polimorfici?


770

Sono un po 'confuso su come i generici Java gestiscono l'ereditarietà / il polimorfismo.

Supponiamo la seguente gerarchia:

Animale (genitore)

Cane - Gatto (Bambini)

Supponiamo quindi di avere un metodo doSomething(List<Animal> animals). Secondo tutte le regole di ereditarietà e polimorfismo, suppongo che a List<Dog> is a List<Animal>e a List<Cat> is a List<Animal>- e quindi uno dei due potrebbe essere passato a questo metodo. Non così. Se voglio ottenere questo comportamento, devo dire esplicitamente al metodo di accettare un elenco di qualsiasi sottoclasse di Animali dicendo doSomething(List<? extends Animal> animals).

Capisco che questo è il comportamento di Java. La mia domanda è: perché ? Perché il polimorfismo è generalmente implicito, ma quando si tratta di generici deve essere specificato?


17
E una domanda grammaticale totalmente non correlata che mi dà fastidio ora - il mio titolo dovrebbe essere "perché non sono i generici di Java" o "perché non sono i generici di Java" ?? "Generici" è plurale a causa della s o singolare perché è un'entità?
froadie,

25
i generici come fatto in Java sono una forma molto povera di polimorfismo parametrico. Non fidarti troppo di loro (come una volta), perché un giorno colpirai duramente i loro patetici limiti: il chirurgo estende Handable <Scalpel>, Handable <Sponge> KABOOM! Non non calcolare [TM]. C'è la tua limitazione generica Java. Qualsiasi OOA / OOD può essere tradotto bene in Java (e l'MI può essere fatto molto bene usando le interfacce Java) ma i generici semplicemente non lo tagliano. Stanno bene per le "raccolte" e la programmazione procedurale che ha detto (che è ciò che la maggior parte dei programmatori Java fa comunque ...).
SyntaxT3rr0r

8
La super classe dell'elenco <Dog> non è Elenco <Animale> ma Elenco <?> (Ovvero elenco di tipo sconosciuto). Generics cancella le informazioni sul tipo nel codice compilato. Questo viene fatto in modo che il codice che utilizza generics (java 5 e versioni successive) sia compatibile con le versioni precedenti di java senza generics.
rai.skumar,


9
@froadie dal momento che nessuno sembrava rispondere ... dovrebbe essere sicuramente "perché non sono i generici di Java ...". L'altro problema è che "generico" è in realtà un aggettivo, e quindi "generici" si riferisce a un nome plurale abbandonato modificato da "generico". Si potrebbe dire "quella funzione è generica", ma sarebbe più ingombrante che dire "quella funzione è generica". Tuttavia, è un po 'ingombrante dire "Java ha funzioni e classi generiche", anziché solo "Java ha generici". Come qualcuno che ha scritto la tesi del loro maestro sugli aggettivi, penso che ti sia imbattuto in una domanda molto interessante!
Dantiston,

Risposte:


916

No, a nonList<Dog> è a . Considera cosa puoi fare con un - puoi aggiungere qualsiasi animale ad esso ... incluso un gatto. Ora, puoi logicamente aggiungere un gatto a una cucciolata di cuccioli? Assolutamente no.List<Animal>List<Animal>

// 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?

All'improvviso hai un gatto molto confuso.

Ora, non puoi aggiungere a Cata List<? extends Animal>perché non sai che è a List<Cat>. Puoi recuperare un valore e sapere che sarà un valore Animal, ma non puoi aggiungere animali arbitrari. È vero il contrario List<? super Animal>: in tal caso puoi aggiungerne uno Animalin modo sicuro, ma non sai nulla di ciò che potrebbe essere recuperato da esso, perché potrebbe essere un List<Object>.


50
È interessante notare che ogni elenco di cani è davvero un elenco di animali, proprio come ci dice l'intuizione. Il punto è che non ogni elenco di animali è un elenco di cani, quindi il problema è mutare l'elenco aggiungendo un gatto.
Ingo,

66
@Ingo: No, non proprio: puoi aggiungere un gatto a un elenco di animali, ma non puoi aggiungere un gatto a un elenco di cani. Un elenco di cani è solo un elenco di animali se lo si considera in un senso di sola lettura.
Jon Skeet,

12
@JonSkeet - Certo, ma chi sta imponendo che la creazione di un nuovo elenco da un gatto e un elenco di cani cambi effettivamente l'elenco dei cani? Questa è una decisione di implementazione arbitraria in Java. Uno che va contro la logica e l'intuizione.
Ingo,

7
@Ingo: non avrei usato questo "certamente" per cominciare. Se hai un elenco che dice in alto "Hotel che potremmo voler visitare" e quindi qualcuno ha aggiunto una piscina ad esso, pensi che sia valido? No - è un elenco di hotel, che non è un elenco di edifici. E non è come se avessi persino detto "Un elenco di cani non è un elenco di animali" - L'ho messo in termini di codice , in un carattere di codice. Non credo davvero che ci sia alcuna ambiguità qui. L'uso della sottoclasse sarebbe comunque errato: si tratta di compatibilità delle assegnazioni, non di sottoclassi.
Jon Skeet,

14
@ruakh: il problema è che stai puntando al tempo di esecuzione qualcosa che può essere bloccato in fase di compilazione. E direi che la covarianza dell'array era un errore progettuale per cominciare.
Jon Skeet,

84

Quello che stai cercando si chiama parametri di tipo covariante . Ciò significa che se un tipo di oggetto può essere sostituito con un altro in un metodo (ad esempio, Animalpuò essere sostituito con Dog), lo stesso vale per le espressioni che usano quegli oggetti (quindi List<Animal>potrebbe essere sostituito con List<Dog>). Il problema è che la covarianza non è sicura per gli elenchi mutabili in generale. Supponi di avere un List<Dog>, e che sia usato come un List<Animal>. Cosa succede quando provi ad aggiungere un gatto a questo List<Animal>che è davvero un List<Dog>? Consentire automaticamente che i parametri di tipo siano covarianti interrompe il sistema di tipi.

Sarebbe utile aggiungere la sintassi per consentire ai parametri di tipo di essere specificati come covarianti, il che evita le ? extends Foodichiarazioni del metodo in, ma che aggiunge ulteriore complessità.


44

La ragione a List<Dog>non è a List<Animal>, è che, ad esempio, puoi inserire a Catin a List<Animal>, ma non in a List<Dog>... puoi usare i caratteri jolly per rendere i generici più estensibili ove possibile; per esempio, leggere da a List<Dog>è simile a leggere da a List<Animal>- ma non scrivere.

I generici in linguaggio Java e la sezione sui generici dei tutorial Java hanno una spiegazione molto buona e approfondita sul perché alcune cose sono o non sono polimorfiche o permesse con i generici.


36

Un punto che penso dovrebbe essere aggiunto a ciò che menzionano altre risposte è che nel frattempo

List<Dog>non-a List<Animal> in Java

è anche vero che

Un elenco di cani è un elenco di animali in inglese (beh, sotto una ragionevole interpretazione)

Il modo in cui funziona l'intuizione del PO - che è completamente valido ovviamente - è quest'ultima frase. Tuttavia, se applichiamo questa intuizione, otteniamo un linguaggio che non è di tipo Java nel suo sistema di tipi: supponiamo che la nostra lingua consenta l'aggiunta di un gatto al nostro elenco di cani. Cosa significherebbe? Significherebbe che l'elenco cessa di essere un elenco di cani e rimane semplicemente un elenco di animali. E un elenco di mammiferi e un elenco di quadrapedi.

Per dirla in altro modo: A List<Dog>in Java non significa "un elenco di cani" in inglese, significa "un elenco che può avere cani e nient'altro".

Più in generale, l'intuizione di OP si presta a un linguaggio in cui le operazioni sugli oggetti possono cambiare il loro tipo , o meglio, il tipo (i) di un oggetto è una funzione (dinamica) del suo valore.


Sì, il linguaggio umano è più confuso. Tuttavia, una volta aggiunto un animale diverso all'elenco dei cani, rimane comunque un elenco di animali, ma non più un elenco di cani. La differenza di essere, un essere umano, con la logica fuzzy, di solito non ha problemi a capirlo.
Vlasec,

Come qualcuno che trova ancora più confusi i confronti costanti con gli array, questa risposta mi ha inchiodato. Il mio problema era l'intuizione del linguaggio.
FLonLon,

32

Direi che il punto centrale di Generics è che non lo consente. Considera la situazione con gli array, che consentono quel tipo di covarianza:

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

Quel codice viene compilato correttamente, ma genera un errore di runtime ( java.lang.ArrayStoreException: java.lang.Booleannella seconda riga). Non è typesafe. Il punto di Generics è aggiungere la sicurezza del tipo di tempo di compilazione, altrimenti si potrebbe semplicemente attenersi a una classe semplice senza generici.

Ora ci sono momenti in cui devi essere più flessibile e questo è ciò che serve ? super Classe ? extends Class. Il primo è quando è necessario inserire in un tipo Collection(ad esempio), e il secondo è quando è necessario leggere da esso, in modo sicuro. Ma l'unico modo per fare entrambe le cose contemporaneamente è avere un tipo specifico.


13
Probabilmente, la covarianza dell'array è un bug di progettazione del linguaggio. Si noti che a causa della cancellazione del tipo, lo stesso comportamento è tecnicamente impossibile per la raccolta generica.
Michael Borgwardt,

" Direi che il punto centrale di Generics è che non lo consente ". Non si può mai essere sicuri: i sistemi di tipi di Java e Scala sono sbagliati: la crisi esistenziale dei puntatori null (presentato a OOPSLA 2016) (da quando sembra corretto)
David Tonhofer

15

Per comprendere il problema è utile fare un confronto con gli array.

List<Dog>non è una sottoclasse di List<Animal>.
Ma Dog[] è una sottoclasse di Animal[].

Le matrici sono riutilizzabili e covarianti .
Reifiable significa che le informazioni sul loro tipo sono completamente disponibili in fase di esecuzione.
Pertanto, gli array forniscono sicurezza del tipo di runtime ma non sicurezza del tipo di compilazione.

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

È viceversa per i generici: i
generici vengono cancellati e invarianti .
Pertanto i generici non possono fornire la sicurezza del tipo di runtime, ma forniscono la sicurezza del tipo in fase di compilazione.
Nel codice seguente se i generici fossero covarianti sarà possibile inquinare il mucchio alla linea 3.

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

2
Si potrebbe sostenere che, proprio per questo, gli array in Java sono rotti ,
leonbloy,

La matrice di covariante è una "caratteristica" del compilatore.
Cristik

6

Le risposte fornite qui non mi hanno convinto del tutto. Quindi, invece, faccio un altro esempio.

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

suona bene, vero? Ma puoi solo passare Consumers e Suppliers per Animals. Se hai un Mammalconsumatore, ma un Duckfornitore, non dovrebbero adattarsi, sebbene entrambi siano animali. Per impedire ciò, sono state aggiunte ulteriori restrizioni.

Invece di quanto sopra, dobbiamo definire le relazioni tra i tipi che utilizziamo.

Per esempio.,

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

si assicura che possiamo utilizzare solo un fornitore che ci fornisce il giusto tipo di oggetto per il consumatore.

OTOH, potremmo anche fare

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

dove andiamo dall'altra parte: definiamo il tipo di Suppliere limitiamo che può essere inserito in Consumer.

Possiamo persino fare

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

dove, avendo le relazioni intuitive Life-> Animal-> Mammal-> Dog, Catecc., potremmo persino mettere Mammalun Lifeconsumatore, ma non Stringun Lifeconsumatore.


1
Tra le 4 versioni, # 2 è probabilmente errato. per esempio non possiamo chiamarlo (Consumer<Runnable>, Supplier<Dog>)mentre while Dogè il sottotipo diAnimal & Runnable
ZhongYu

5

La logica di base per tale comportamento è che Genericssegue un meccanismo di cancellazione del tipo. Quindi in fase di esecuzione non hai modo di identificare il tipo di collectiondiversamente da arraysdove non esiste un tale processo di cancellazione. Quindi tornando alla tua domanda ...

Supponiamo quindi che esista un metodo come indicato di seguito:

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

Ora, se java consente al chiamante di aggiungere Elenco di tipo Animale a questo metodo, è possibile aggiungere qualcosa di sbagliato alla raccolta e anche in fase di esecuzione verrà eseguito a causa della cancellazione del tipo. Mentre in caso di array otterrai un'eccezione di runtime per tali scenari ...

Quindi, in sostanza, questo comportamento è implementato in modo tale che non si possa aggiungere qualcosa di sbagliato nella raccolta. Ora credo che il tipo di cancellazione esista in modo da garantire la compatibilità con java legacy senza generici ....


4

Il sottotipo è invariante per i tipi con parametri. Anche se dura la classe Dogè un sottotipo di Animal, il tipo con parametri List<Dog>non è un sottotipo di List<Animal>. Al contrario, il sottotipo covariante viene utilizzato dagli array, quindi il tipo di array Dog[]è un sottotipo di Animal[].

Il sottotipo invariante garantisce che i vincoli di tipo applicati da Java non vengano violati. Considera il seguente codice fornito da @Jon Skeet:

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

Come affermato da @Jon Skeet, questo codice è illegale, perché altrimenti violerebbe i vincoli di tipo restituendo un gatto quando previsto da un cane.

È istruttivo confrontare quanto sopra con il codice analogo per le matrici.

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

Il codice è legale Tuttavia, genera un'eccezione del negozio di array . Un array porta il suo tipo in fase di esecuzione in questo modo JVM può imporre la sicurezza del tipo del sottotipo covariante.

Per capirlo ulteriormente, diamo un'occhiata al bytecode generato dalla javapclasse seguente:

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

Usando il comando javap -c Demonstration, questo mostra il seguente bytecode Java:

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
}

Osservare che il codice tradotto dei corpi dei metodi è identico. Il compilatore ha sostituito ogni tipo con parametri con la sua cancellazione . Questa proprietà è fondamentale, nel senso che non ha interrotto la retrocompatibilità.

In conclusione, la sicurezza di runtime non è possibile per i tipi con parametri, poiché il compilatore sostituisce ogni tipo con parametri con la sua cancellazione. Questo rende i tipi con parametri non sono altro che zucchero sintattico.


3

In realtà è possibile utilizzare un'interfaccia per ottenere ciò che si desidera.

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

}

è quindi possibile utilizzare le raccolte utilizzando

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

1

Se sei sicuro che le voci dell'elenco siano sottoclassi di quel determinato super tipo, puoi lanciare l'elenco usando questo approccio:

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

Ciò è utile quando si desidera passare l'elenco in un costruttore o scorrere su di esso


2
Ciò creerà più problemi di
quanti ne

Se provi ad aggiungere un gatto all'elenco, sicuramente creerà problemi, ma per scopi di loop penso che sia l'unica risposta non dettagliata.
Sagitt

1

La risposta e altre risposte sono corrette. Aggiungerò quelle risposte con una soluzione che ritengo possa essere utile. Penso che questo si presenti spesso nella programmazione. Una cosa da notare è che per le raccolte (elenchi, set, ecc.) Il problema principale è l'aggiunta alla raccolta. Ecco dove le cose si rompono. Anche la rimozione è OK.

Nella maggior parte dei casi, possiamo usare Collection<? extends T>piuttosto allora Collection<T>e quella dovrebbe essere la prima scelta. Tuttavia, sto trovando casi in cui non è facile farlo. È in discussione se questa sia sempre la cosa migliore da fare. Sto presentando qui una classe DownCastCollection che può portare convert a Collection<? extends T>in Collection<T>(possiamo definire classi simili per List, Set, NavigableSet, ..) da usare quando si utilizza l'approccio standard è molto scomodo. Di seguito è riportato un esempio di come usarlo (potremmo anche usarlo Collection<? extends Object>in questo caso, ma lo sto semplificando per illustrare usando DownCastCollection.

/**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));
}

Ora la classe:

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

}

}


Questa è una buona idea, tant'è che esiste già in Java SE. ; )Collections.unmodifiableCollection
Radiodef,

1
Giusto ma la raccolta che definisco può essere modificata.
dan del

Sì, può essere modificato. Collection<? extends E>tuttavia gestisce già quel comportamento correttamente, a meno che non lo si usi in un modo che non sia sicuro per il tipo (ad esempio, lanciarlo su qualcos'altro). L'unico vantaggio che vedo è che, quando chiami l' addoperazione, viene generata un'eccezione anche se l'hai lanciata.
Vlasec,

0

Facciamo l'esempio del tutorial JavaSE

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) {
        ...
    }
}

Quindi perché un elenco di cani (cerchi) non dovrebbe essere considerato implicitamente un elenco di animali (forme) è a causa di questa situazione:

// drawAll method call
drawAll(circleList);


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

Quindi gli "architetti" Java avevano 2 opzioni che risolvono questo problema:

  1. non considerare che un sottotipo sia implicitamente un supertipo e dare un errore di compilazione, come accade ora

  2. considera il sottotipo come un supertipo e limita durante la compilazione il metodo "add" (quindi nel metodo drawAll, se viene passato un elenco di cerchi, sottotipo di forma, il compilatore dovrebbe rilevarlo e limitarti con errore di compilazione nel fare quello).

Per ovvie ragioni, quello ha scelto il primo modo.


0

Dovremmo anche prendere in considerazione il modo in cui il compilatore minaccia le classi generiche: in "istanze" un tipo diverso ogni volta che riempiamo gli argomenti generici.

Così abbiamo ListOfAnimal, ListOfDog, ListOfCat, ecc, che sono le classi distinte che finiscono per essere "creato" dal compilatore quando specificare gli argomenti generici. E questa è una gerarchia piatta (in realtà per quanto riguarda Listnon è affatto una gerarchia).

Un altro argomento per cui la covarianza non ha senso nel caso di classi generiche è il fatto che alla base tutte le classi sono uguali - sono Lististanze. La specializzazione a Listcompilando l'argomento generico non estende la classe, ma la fa funzionare solo per quel particolare argomento generico.


0

Il problema è stato ben identificato. Ma c'è una soluzione; fai qualcosa di generico:

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

ora puoi chiamare doSomething con Elenco <Cane> o Elenco <Gatto> o Elenco <Animale>.


0

un'altra soluzione è quella di creare un nuovo elenco

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

0

Oltre alla risposta di Jon Skeet, che utilizza questo codice di esempio:

// 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?

Al livello più profondo, il problema qui è quello dogse animalscondividere un riferimento. Ciò significa che un modo per farlo funzionare sarebbe quello di copiare l'intero elenco, il che spezzerebbe l'uguaglianza di riferimento:

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

Dopo aver chiamato List<Animal> animals = new ArrayList<>(dogs);, non è possibile successivamente assegnare direttamente animalsa uno dogso cats:

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

pertanto non è possibile inserire Animalnell'elenco secondario il sottotipo errato , poiché non esiste un sottotipo errato - è ? extends Animalpossibile aggiungere qualsiasi oggetto del sottotipo animals.

Ovviamente, questo cambia la semantica, poiché le liste animalse dogsnon sono più condivise, quindi l'aggiunta a una lista non si aggiunge all'altra (che è esattamente quello che vuoi, per evitare il problema che a Catpotrebbe essere aggiunta a una lista che è solo dovrebbe contenere Dogoggetti). Inoltre, copiare l'intero elenco può essere inefficiente. Tuttavia, questo risolve il problema dell'equivalenza del tipo, rompendo l'uguaglianza di riferimento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.