2015-05-21 17 views
51

Ho battuto la testa contro questo per un po 'e ho pensato che forse alcuni occhi nuovi vedranno il problema; Grazie per il tuo tempo.Java Generics Puzzler, estensione di una classe e utilizzo di caratteri jolly

import java.util.*; 

class Tbin<T> extends ArrayList<T> {} 
class TbinList<T> extends ArrayList<Tbin<T>> {} 

class Base {} 
class Derived extends Base {} 

public class Test { 
    public static void main(String[] args) { 
    ArrayList<Tbin<? extends Base>> test = new ArrayList<>(); 
    test.add(new Tbin<Derived>()); 

    TbinList<? extends Base> test2 = new TbinList<>(); 
    test2.add(new Tbin<Derived>()); 
    } 
} 

Utilizzo di Java 8. Sembra a me come la creazione diretta del contenitore in test è equivalente al contenitore in test2, ma il compilatore dice:

Test.java:15: error: no suitable method found for add(Tbin<Derived>) 
    test2.add(new Tbin<Derived>()); 
     ^

Come faccio a scrivere Tbin e TbinList quindi l'ultima riga è accettabile?

Nota che in realtà sto aggiungendo il dattiloscritto Tbin s che è il motivo per cui ho specificato Tbin<Derived> nell'ultima riga.

+0

In Eclipse il messaggio di errore è 'Il metodo add (Tbin ) nel tipo ArrayList > non è applicabile per gli argomenti (Tbin ) '. – dimo414

+0

Prova questo 'ArrayList > test3 = new TbinList <>(); '. Davvero molto interessante. – Radiodef

+0

@Radiodef Eclipse mostra un errore dicendo che "non può dedurre argomenti di tipo per TbinList <>". – TNT

risposta

1

OK, ecco la risposta:

import java.util.*; 

class Tbin<T> extends ArrayList<T> {} 
class TbinList<T> extends ArrayList<Tbin<? extends T>> {} 

class Base {} 
class Derived extends Base {} 

public class Test { 
    public static void main(String[] args) { 

    TbinList<Base> test3 = new TbinList<>(); 
    test3.add(new Tbin<Derived>()); 

    } 
} 

Come mi aspettavo, specie di evidente una volta l'ho visto. Ma un sacco di battute per arrivare qui. I generici Java sembrano semplici se si guarda solo al codice funzionante.

Grazie a tutti per essere una cassa di risonanza.

+0

Se hai ottenuto questo dalla risposta di tynn, dovresti considerare di accettarlo. http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – Radiodef

+0

Non l'ho fatto. Guarda i tempi. – user1677663

+2

Mostra la risposta di tynn pubblicata per prima sulla mia estremità. Ad ogni modo, è solo un commento di routine. A volte gli utenti ripubblicano perché non sanno sull'accettazione. – Radiodef

3

Si sta utilizzando un carattere jolly limitato (TbinList<? extends Base>> ...). Questo jolly ti impedirà di aggiungere elementi all'elenco. Se vuoi maggiori informazioni, ecco la sezione su Wildcards nella documentazione.

+0

Il tuo link è rotto. –

+0

strano, basta copiare incollato. dammi un secondo, lo aggiusterò – Paul

+0

Che lo aggiusta, grazie! –

1

non è possibile aggiungere oggetti a TbinList<? extends Base>, non è garantito quali oggetti si stanno inserendo nell'elenco. Si suppone di leggere i dati da test2 quando si utilizza jolly extends

Se dichiarato come TbinList<? extends Base> che significa che è tutte le sottoclassi della classe base o classe di base stessa, e quando si inizializza esso si utilizza diamanti diversa classe concreta nome, rende il tuo test2 non ovvio che rende più difficile dire quali oggetti possono essere inseriti. Il mio suggerimento è di evitare che tale dichiarazione sia pericolosa, potrebbe non avere errori di compilazione ma è un codice orribile, potresti aggiungere qualcosa, ma potresti anche aggiungere la cosa SBAGLIATA che infrange il tuo codice.

+1

È possibile, in realtà. 'test2.add (new Tbin <>());' compila bene, e aggiunge chiaramente una lista vuota quando stampo 'test2' prima e dopo. – azurefrog

+0

quando dico 'you cannot', intendo che è pericoloso aggiungere tipi di oggetti poco chiari :) Penso che dovrei usare 'you'd better not' – haifzhan

1

È possibile definire i tipi generici come segue:

class Tbin<T> extends ArrayList<T> {} 
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {} 

Poi si creerebbe istanza come:

TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>(); 
test2.add(new Tbin<Derived>()); 
10

Sostituzione della definizione di TbinList con

class TbinList<T> extends ArrayList<Tbin<? extends T>> {} 

e definendo test2 con

TbinList<Base> test2 = new TbinList<>(); 

risolverebbe invece il problema.

Con la tua definizione sei finito con un ArrayList<Tbin<T>> dove T è una qualsiasi classe fissa che si estende Base.

+0

Questo è ragionevole, supponendo che non abbiano mai bisogno solo per es. un 'TbinList' che è equivalente a un' ArrayList > '. – Radiodef

37

Questo accade a causa del modo in cui capture conversion opere:

There exists a capture conversion from a parameterized type G<T1,...,Tn> to a parameterized type G<S1,...,Sn> , where, for 1 ≤ i ≤ n :

  • If Ti is a wildcard type argument of the form ? extends Bi , then Si is a fresh type variable [...].

Capture conversion is not applied recursively.

Annotare il bit finale. Allora, che cosa questo significa è che, dato un tipo come questo:

Map<?, List<?>> 
//  │ │ └ no capture (not applied recursively) 
//  │ └ T2 is not a wildcard 
//  └ T1 is a wildcard 

Solo "al di fuori" jolly vengono catturati. Il Map jolly chiave viene catturato, ma il List elemento jolly non è. Questo è il motivo per cui, ad esempio, siamo in grado di aggiungere a un List<List<?>>, ma non un List<?>. La posizione del jolly è ciò che conta.

Portare questo oltre a TbinList, se abbiamo un ArrayList<Tbin<?>>, il jolly è in un luogo dove non viene catturato, ma se abbiamo un TbinList<?>, il jolly è in un luogo in cui viene catturato.

Come ho accennato nei commenti, un test molto interessante è questo:

ArrayList<Tbin<? extends Base>> test3 = new TbinList<>(); 

Otteniamo questo errore:

error: incompatible types: cannot infer type arguments for TbinList<> 
    ArrayList<Tbin<? extends Base>> test3 = new TbinList<>(); 
                 ^
    reason: no instance(s) of type variable(s) T exist so that TbinList<T> conforms to ArrayList<Tbin<? extends Base>>

Quindi non c'è modo di farlo funzionare così com'è. Una delle dichiarazioni di classe deve essere cambiata.


Inoltre, pensa in questo modo.

Supponiamo di avere:

class Derived1 extends Base {} 
class Derived2 extends Base {} 

E dal momento che un jolly consente subtyping, siamo in grado di fare questo:

TbinList<? extends Base> test4 = new TbinList<Derived1>(); 

dovremmo essere in grado di aggiungere un Tbin<Derived2>-test4? No, questo sarebbe inquinamento da cumulo. Potremmo finire con Derived2 s galleggianti intorno in un TbinList<Derived1>.

+0

La mia [risposta] (http://stackoverflow.com/a/30385058/1129332) interrompe la tua affermazione, con un trucco per aiutare il metodo a prevenire la cattura. –

+0

@Ilya_Gazman La tua risposta usa un cast non controllato e non funziona per un 'TbinList '. – Radiodef

+0

Lo modifica ora. L'idea è la stessa. Perché il cast deselezionato è sbagliato in questo caso? –