2009-08-14 21 views
6

Quando si eseguono alcune cose non particolarmente elaborate con Java, ho riscontrato un errore con i generici che non ero in grado di capire perché non funziona. Il codice è:Errore del compilatore Java generics

package test; 
import java.util.*; 
public class TestClass { 
    public static class A extends C{} 
    public static class B extends C{} 
    public static class C{} 
    public static class D<T>{} 
    public static class E<T>{} 

    public static void main(String args[]){ 
     E<D<? extends C>> a = new E<D<A>>(); 
     E<D<? extends Object>> b = new E<D<? extends C>>(); 
     E<D<? extends A>> c = new E<D<A>>(); 
     E<D<? super A>> d = new E<D<A>>(); 
     D<? extends C> e = new D<A>(); 
     D<? extends A> f = new D<A>(); 
     D<? extends A> g = new D<A>(); 
    } 
} 

L'errore che ottengo quando si compila è:

 
test/TestClass.java:11: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> a = new E>(); 
          ^
test/TestClass.java:12: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> b = new E>(); 
           ^
test/TestClass.java:13: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> c = new E>(); 
          ^
test/TestClass.java:14: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> d = new E>(); 
         ^
4 errors 

Se E<D<? extends C>> viene trovato, che dovrebbe sicuramente abbinare E<D<? extends Object>>, giusto? O mi sono perso qualcosa?

+3

questa è una buona domanda per la prossima edizione di Java Puzzlers http://www.javapuzzlers.com/ – dfa

+0

Credo che tu sia inciampato in un caso limite. Molto interessante. –

+0

Questo può essere utile se riesci a capirlo: http://bit.ly/3RrNV3 – teabot

risposta

1

Verificare il tipo dell'oggetto restituito dalla chiamata Arrays.asList. Vorrei indovinare restituisce un elenco < D <? estende l'oggetto C > >, che non può essere assegnato a un elenco < D <? estende l'oggetto > >.

+0

Guarda la nuova riga che ho appena postato. E perché non è castable? – arnebef

+0

Tutto si riduce a quello che ho commentato sulla risposta di GrzegorzOledzki. I riferimenti ti consentirebbero di utilizzare una gamma più ampia di classi, quindi la tua classe generica originale accetta. – Zed

2

Forse questo potrebbe aiutare a capire:

ArrayList<Object> aList = new ArrayList<String>(); 

Questo non compilare o con un errore simile.

EDIT: consulta con: http://www.ibm.com/developerworks/java/library/j-jtp01255.html

+0

Questo non viene compilato, poiché utilizzando il riferimento a elenco è possibile inserire oggetti nell'elenco, tuttavia si suppone che contenga solo stringhe. – Zed

+2

Questo è il suo punto. –

1

<? extends C> specifica limiti superiori del tipo, il che significa che il tipo deve essere esteso da C. <? extends Object> è apparentemente più generale, quindi è incompatibile.

Pensa a questo caso d'uso. Specificando un limite superiore, ci si aspetta che venga implementata una determinata interfaccia minima. Supponiamo che tu abbia un metodo doIt() dichiarato in C. Qualsiasi classe che estende C avrebbe quel metodo ma non tutte le classi che estendono l'oggetto (che è ogni classe in Java).

+0

Hai sbagliato strada, cerco di assegnare a arnebef

+0

Potrebbe essere necessario pensare in modo errato :) Questo è esattamente l'opposto della sicurezza del tipo di assegnazione dell'oggetto. –

+0

Nessuno dei due avrebbe funzionato, se avesse senso o meno. I tipi generici devono corrispondere esattamente, non solo estendendo i tipi. – Jorn

0

È peggio di così. Non si può nemmeno fare questo:

List<D<?>> lDQ = Arrays.asList(new D<A>()); 

E 'più chiaro se si cambia il programma come segue:

List<D<? extends C>> a = Arrays.asList(new D<A>(), new D<B>()); //compiles 
List<D<? extends Object>> b = a; //error 

sostanza, si sta dichiarando la b come qualcosa che può accettare contenuti generali (D<nulla>), ma l'elenco si sta assegnando ad esso è qualcosa che accetta solo i contenuti più specifici (D<più vicino superclasse comune di a e B>).

Dichiarare b come List<D<? extends Object>> implica che si potrebbe, ad esempio, b.add(new D<String>()), ma in realtà è un List<D<? extends C>>, così non si può.

2

Questo è praticamente lo stesso caso di quello precedente. In sostanza, in un caso di farmaci generici, non si è mai permesso di compiere questa assigment:

Pensate a questo esempio:

ArrayList<Object> alist = new ArrayList<Number>(); 

Questo non viene compilato perché non è il tipo di sicurezza. Si potrebbe eventualmente aggiungere Strings a lista.Stai provando ad assegnare una lista di oggetti che sono garantiti come numeri ma può essere qualsiasi numero, ad una lista che ti garantisce solo di contenere oggetti ma che possono essere qualsiasi oggetto. Se il compilatore avesse permesso questo caso, avrebbe allentato la restrizione su quali tipi di oggetti potevano entrare nella lista. Questo è il motivo per cui è necessario utilizzare il carattere jolly, come ad esempio:?

ArrayList<? extends Object> alist = new ArrayList<Number>(); 

al compilatore ArrayList<? extends Object>, significa "un ArrayList di un certo tipo specifico '?' che non conosco, ma che conosco estende Object. Questo ArrayList è garantito per contenere solo elementi di questo sconosciuto '?' digita, e quindi contiene solo oggetti ". In questo caso, tuttavia, il compilatore non ti permetterà di fare alist.add (2). Perché è così, perché il compilatore non conosce il tipo di elementi dell'elenco e non può garantire che sia consentito inserire oggetti Integer in esso.

Hai ragione nel pensare che D<? extends Object> è un supertipo di D<? extends C>. Tuttavia, List<D<? extends Object>> non è un sottotipo di List<D<? extends C>>, si dovrebbe usare List<? extends D<? extends C>>.

Il tuo caso è sostanzialmente equivalente a

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); 

Lei ha lo stesso problema di cui sopra, l'elenco sul lato destro della strada può contenere solo oggetto della classe D il cui parametro tipo è C, e si sta tentando di assegnarlo a una lista (sul lato sinistro) può contenere oggetti di classe D il cui parametro di tipo può essere qualsiasi oggetto.

Quindi, se il compilatore ha permesso il tuo codice non sarebbe sicuro, e il seguente non avrebbe funzionato.

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); //< not type safe 
alist.add(new D<Number>); //< oops 

In breve, quello che vi serve per il vostro esempio specifico è il seguente:

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends Object>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is identical 
List<D<? extends C>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends C>> c = Arrays.asList(new D<A>()); 

// type parameter of left hand side is identical 
List<D<A>> c = Arrays.asList(new D<A>()); 

Spero che questo aiuti.

0

Il fatto è che List<D<? extends Object>> definisce fondamentalmente una nuova classe e lo fa anche List<D<? extends C>>. Anche quando c estende Object che non significa che List<D<? extends C>> si estende List<D<? extends Object>> e che sarebbe necessario che l'assegno funzioni.

Althoug questo series of articles è scritto per la piattaforma .NET la descrizione del problema è ancora valido per generici Java

+0

È necessario sfuggire a quelle parentesi angolari con i backtick. –

0

Non si può ereditare in farmaci generici-parametro. Supponiamo di avere i seguenti riferimenti:

List<Object> a; 
List<String> b; 

Ora si assegna entrambi la stessa lista: a = b;

Se po 'di codice esegue le seguenti operazioni:

a.add(new Integer(1)); 

Cosa succede se qualcuno fa il seguente:

String s = b.get(0); 

Si potrebbe ottenere un numero intero una lista di stringhe. Questo non dovrebbe funzionare Ecco perché Elenco ed Elenco sono incompatibili, anche se una stringa può essere assegnata a un riferimento all'oggetto. I generici non funzionano con l'ereditarietà.

-1

No.
<?estende C > non è la stessa di <? estende Object >
Se così fosse, porterebbe anche il presupposto (non stabilita) - C estende Object, e portare a dire ...

class C{ public void doSomethingMEaningful(); };

List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88)); ...

allAtSea.addAll(allObjects); ...

allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88


Il C++ FAQ fornisce un esempio di lucida questo a 21.3

+0

Non penso che un esempio C++ aiuti in una domanda Java. – Jorn

+0

Mi permetto di dissentire, se l'elenco di qualcosa è uguale a un elenco di qualcosa, altrimenti si tratta di comprendere l'ereditarietà e la sostituibilità.
Questo, IMO, riguarda i fondamenti OO; quindi il riferimento a C++ dovrebbe essere accettabile – Everyone

+0

L'esempio non è ancora corretto (o addirittura aiuta, IMO). La riga allAtSea.addAll() non viene compilata e neanche theoObjects.add(). – Jorn

0

Penso che questo sarà più chiaro se estendiamo il tuo esempio. Mettiamo un po 'di funzionalità in E ed elaborare sulla prima riga del metodo principale:

public static class E<T>{ 
    private final T thing; 

    public void setThing(T thing) { 
     this.thing = thing; 
    } 

    public T getThing() { 
     return thing; 
    } 
} 

public static void main(String[] args) { 
    E<D<? extends C>> a1; 
    E<D<A>> a2 = new E<D<A>>(); 
    a1 = a2; // this won't compile, but why? 

    // these things are permissible on a1: 
    a1.setThing(new D<A>()); 
    a2.setThing(new D<B>()); 

    // now let's try doing the same thing to a2: 
    a2.setThing(new D<A>()); 
    a2.setThing(new D<B>()); // oops 
} 

L'ultima riga del nuovo metodo principale è il motivo per A1 non può essere impostato per a2. Se il compilatore consente di farlo, sarà possibile inserire un D <B> in un contenitore dichiarato per contenere solo D <A> s.

Il motivo per cui questo non è evidente dal vostro esempio originale è perché non avete creato una variabile indipendente che fa riferimento l'oggetto utilizzando la più restrittiva E < D <Un> > battitura. Ma si spera che ora si possa vedere che se esiste un tale riferimento, il tipo di sicurezza sarebbe compromesso dall'assegnazione che si è tentato di fare.

1

Quando si assegna ad una variabile (E<T>) con un non-wildcard tipo generico T, l'oggetto assegnato deve avere esattamente T come tipo generico (inclusi tutti i parametri di tipo generico di T, jolly e non carattere jolly). Nel tuo caso T è D<A>, che non è dello stesso tipo di D<? extends C>.

Che cosa si può fare, perché D<A> è assegnabile a D<? extends C>, è utilizzare il tipo di carattere jolly:

E<? extends D<? extends C>> a = new E<D<A>(); 
+0

Penso che questa risposta spieghi meglio di tutti. Puoi fare E > e = nuovo E >(); ma non E > e = nuovo E >(); – Jorn