2013-03-07 12 views
13

Supponiamo Ho il seguente:Perché devo effettuare una chiamata generica in modo esplicito?

public <T extends Widget> List<T> first(T n) { 
    return first(n.getClass()); 
} 
public <T extends Widget> List<T> first(Class<T> n) { 
    return new ArrayList<>(); 
} 

Il compilatore si lamenta alla linea 3 con "incompatible types; required: java.util.List<T>; found: java.util.List<capture#1 of ? extends my.app.Widget>". Che non capisco perché Mi sembra ragionevole che il tipo T non possa mai cambiare in nessun caso se non un sottotipo.

Questo può essere risolto tramite casting esplicito, anche se non so perché è richiesto.

public <T extends Widget> List<T> first(T n) { 
    return (List<T>)first(n.getClass()); 
} 
public <T extends Widget> List<T> first(Class<T> n) { 
    return new ArrayList<>(); 
} 

Potrebbe essere un errore del compilatore?

Nota Sto usando JDK 1.7.0_15:

java version "1.7.0_15" 
Java(TM) SE Runtime Environment (build 1.7.0_15-b03) 
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode) 

risposta

7

Per esattamente la ragione per cui hai dichiarato, il parametro type potrebbe effettivamente essere un supertipo del tipo di runtime reale dell'oggetto che hai passato!T#getClass() non restituisce Class<T>, restituisce Class<? extends T>

Number number = Integer.valueOf(1); 
List<Number> list = first(number); 

Quando si chiama n.getClass() in fase di esecuzione sta per tornare Integer.class, non Number.class, eppure si sta tentando di assegnare il risultato a List<Number>! Il compilatore non ha modo di sapere quale sarà il tipo di runtime reale, conosce solo al massimo lo List<? extends Number>. Costringervi a mettere nel cast è il suo modo di dire "Non posso garantire per la sicurezza di questa operazione, è sulla vostra parola che è corretto".

Ogni volta che è impossibile per il compilatore confermare che un'operazione è tipicamente, ti costringerà a eseguire il cast e quindi ad attivare un avviso "non controllato" in modo che abbia fatto il suo compito di farti conoscere il problema.

+0

Ho letto la tua risposta alcune volte e ho pensato di aver capito, ma non sono abbastanza sicuro ora. Diciamo che 'Circle' e' Square' sono sottoclassi di 'Widget'. Se chiamo 'first (Circle.class)' è possibile solo per un 'Elenco 'per tornare, cioè non posso' restituire il nuovo ArrayList 'come il compilatore non me lo consente. –

+4

'Circle.class' è una costante di tempo di compilazione, non * è * la stessa cosa di' new Circle(). GetClass() 'che non viene risolto fino al runtime. Se hai chiamato 'first (Circle.class)' può restituire 'Lista ' bene, ma non se aspetti fino al run time per scoprire il tipo effettivo. – Affe

+0

Aggiungendo alla mia confusione sono sorpreso che non posso anche fare 'Elenco y = primo (n.getClass());', anche se sicuramente la lista restituita può essere solo come hai detto tu, '? estende Widget' che, a quanto ho capito, questo elenco risultante può contenere solo 'Widget' o discendenti? –

4

getClass() restituisce un valore di tipo Class<?>. Quando lo si passa all'altro override di first, si esegue un cast non controllato su Class<? extends Widget>. È deselezionato perché i generici usano "cancellazione di tipo" in fase di esecuzione, il che significa che la JVM non può controllarlo; passare -Xlint:unchecked per vedere questo avviso.

Nota che questo non è Class<T extends Widget>, il che significa che il sistema di tipo non garantisce che il parametro tipo di questo metodo (il primo override first) è la stessa di quella di un essere chiamato (l'altra first).

Così il risultato che si ottiene indietro è di tipo List<? extends Widget> (dove ? extends Widget è capture#1), che non è compatibile con List<T extends Widget>, e così il compilatore genera correttamente un errore.

In questo caso, capita di sapere (anche se il compilatore no) che questa è una cosa ragionevole da fare, quindi è possibile sovrascriverlo con un cast esplicito. Ma in questo caso, perché non basta fare il metodo questo:

public <T extends Widget> List<T> first() { 
    return new ArrayList<T>(); 
} 
+0

Non compila nemmeno – bsiamionau

+0

Cosa no? Il mio ultimo esempio? Ho appena dimenticato il tipo di ritorno (anche se era presumibilmente ovvio). –

+0

Inoltre, avevi ragione, mio ​​errore. – bsiamionau

1

Generics ei rispettivi tipi finali sono determinati in fase di compilazione, non in fase di esecuzione. Al runtime, vengono cancellate tutte le informazioni generiche, questo è noto come Type erasure. sembra che qui si sta cercando di determinare il tipo generico, al momento della chiamata di funzione, che non è supportato in Java

public <T extends Widget> List<T> first(T n) { 
    return first(n.getClass()); 
} 

Qui, è necessario tornare List<T> - un esempio di lista. Un'istanza concreta deve avere un parametro concreto T. Cioè non è possibile restituire List<T extends Widget> - il tipo sarebbe sconosciuto. Cosa memorizzerai in una lista del genere?

public <T extends Widget> List<T> first(Class<T> n) { 
    return new ArrayList<>(); 
} 

Qui si vuole restituire un elenco in base alla classe in dotazione - ma vediamo che in realtà sta creando un ArrayList generico che poi è colato al tipo richiesto. Il problema è che T qui non è la stessa di T nel metodo precedente. Dovresti dichiarare l'intera classe generica con una T, per renderli uguali.

3

È perché n.getClass() restituisce sempre Class<?> anziché Class<T> pertanto il compilatore non può risolvere direttamente questo. Hai sempre bisogno di qui di esprimere esplicitamente. Ad esempio

public <T extends Widget> List<T> first(T n) { 
    return first((Class<T>)n.getClass()); 
} 
Problemi correlati