2010-11-11 9 views
5

Si consideri il seguente codice:Casting per una classe interna con i generici

public class Outer<T> { 

    public class Inner{ 
    } 

    public static <T> Outer<T>.Inner get(){ 
     Object o = new Object(); 
     return (Outer<T>.Inner)o; 
    } 

    public static void main(String[] args) throws Exception { 
     Outer.<String>get(); 
    } 
} 

Questo codice viene compilato con successo in Eclipse, ma non riesce a compilare in javac:

Outer.java:10: ')' expected 
     return (Outer<T>.Inner)o; 
         ^
Outer.java:10: ';' expected 
     return (Outer<T>.Inner)o; 
         ^
Outer.java:10: illegal start of expression 
     return (Outer<T>.Inner)o; 
          ^
3 errors 

È questo un bug in javac o Eclisse?

Se cambio il cast per (Outer.Inner)o si compila, anche se c'è un avvertimento:

Eclipse:

Outer.Inner is a raw type. References to generic type Outer<T>.Inner should be parameterized 

javac:

Outer.java:10: warning: [unchecked] unchecked conversion 
found : Outer.Inner 
required: Outer<T>.Inner 
     return (Outer.Inner)o; 
      ^
1 warning 

Javac versione: 1.6.0_21

+0

Qual è l'avviso? –

+0

aggiunto alla domanda – dogbane

+0

Eeeek !!! anch'io ho provato questo Eclipse va bene con il 1 ° codice, ma javac non è +1 per dirmi qualcosa di nuovo. –

risposta

0

You cann o lancia un Object a qualcosa che non è.

+2

Se si dispone di un riferimento all'oggetto, può essere convertito letteralmente in qualsiasi tipo di riferimento per quanto riguarda il compilatore. L'ispezione ti diceva che questo avrebbe gettato un errore di runtime sicuro, ma non è quello di cui si lamenta il compilatore. –

+0

Heh, ho rimosso quella parte prima che tu rispondessi. Certo, il compilatore potrebbe non interessarsene, ma la VM lo farà. Inoltre, sta usando sia come classe generica che come metodo generico. Non vincerebbe il metodo generico? – Jeremy

+1

il parametro di classe viene ignorato completamente per i metodi statici. La parametrizzazione di una classe si applica solo ai riferimenti alle istanze. –

1

Purtroppo, se si sta trasmettendo da uno Object, non è possibile evitare l'avviso di cast non controllato. Questo perché Object non ha abbastanza informazioni di tipo da solo per avere lo T.

(generici Java sono la cancellazione-based. Pertanto non v'è alcun modo di sapere se un oggetto ha tipo di argomento T in fase di esecuzione --- argomenti di tipo vengono utilizzati al solo tempo di compilazione.)

+1

Non è l'avviso cast non controllato che è un problema. In realtà, Javac non riuscirà a compilare a causa degli errori elencati. In eclissi viene fornito l'avviso perché viene compilato correttamente. –

+1

@Mark: In realtà, se ho letto correttamente la domanda, l'OP potrebbe farcela compilare correttamente usando 'Outer.Inner' in javac. E questo è il meglio che puoi fare, in questo caso. Dovrebbe mai essere consentito "Esterno . Questa è la parte di cui non sono sicuro. –

+0

Sono d'accordo, e non ti ho minimizzato perché è un'osservazione perfettamente valida. Dovrebbe essere "Esterno .Inner"? Non vedo perché no. Il compilatore consente tutti gli altri tipi di cast non controllati (come il cast di un 'Object' ad un' List ', ad esempio), quindi non vedo perché questo è radicalmente diverso. –

0

Il seguente "sistemare "ha funzionato quando ho compilato con javac. Inoltre è stato compilato con successo in Eclipse. Il problema che percepisco è che non puoi creare un nuovo oggetto da una variabile (come quello che hai fatto nel tuo caso). Non so come spiegarlo o convalidare la mia teoria.

/** 
* 
*/ 
package testcases; 

/** 
* @author The Elite Gentleman 
* 
*/ 
public class Outer<T> { 

    public class Inner{ 
    } 

    public static <T> Outer<T>.Inner get(){ 
     //Object o = new Object(); 
     //return (Outer<T>.Inner)o; 
     return new Outer<T>().new Inner(); 
    } 

    public static void main(String[] args) throws Exception { 
     Outer.<String>get(); 
    } 
} 

In sostanza, dal momento interno non è una classe innestata statica, al fine di creare un'istanza di esso, questo è come si dovrebbe farlo:

new Outer<T>().new Inner(); 

Inoltre, Object o = new Object(); non garantisce che l'oggetto o è in realtà un'istanza di tipo Inner classe.


Aggiornamento La mia soluzione ha aiutato con instantiation oggetto e non per oggetto colata di esistenti un'istanza dell'oggetto. Per questo, non ho risposte (ma siamo sviluppatori, penseremo a qualcosa :-)).

Quello che posso pensare è perché non rendere la classe nidificata statica classe Inner?

+1

Non sono sicuro che sia una "correzione" se è completamente diversa dal punto di vista funzionale. Questo sta creando una nuova istanza, l'OP stava lanciando un oggetto esistente. –

+0

+1 per notare che non è una classe nidificata statica –

+0

@ Mark Peters, true, ma per questo esempio, in sostanza, 'Object o = new Object();' non garantisce che l'oggetto 'o' sia di tipo' Inner 'pure, da qui la mia" correzione ". –

0

ci sono due problemi in voi codice:

Il primo è in fase di compilazione: non si può lanciare in modo sicuro un oggetto a un tipo generico come i generici non sono reificati in Java. Il lancio su un generico non istanziato è chiaramente pericoloso per il tuo javac. Temo che la risposta del compilatore a questo dipende dal produttore del compilatore e dalla versione.

In secondo luogo, lanciando un oggetto, di tipo Oggetto, a qualsiasi altro tipo si genera un ClassCastException in fase di esecuzione come in Java le informazioni sul tipo sono contenute nell'oggetto e non cambieranno dopo la creazione.

+0

Non mi interessa cosa accadrà in fase di esecuzione, perché questo è un esempio semplificato. Mi interessa solo perché javac non riesce a compilare il mio codice. – dogbane

+0

Quindi dovresti dare la tua versione di javac perché ho già fatto cast simili e ho ricevuto solo l'avviso 'non controllato', anche se non riesco a controllare la versione di javac in questo momento. – Guillaume

+0

versione javac: 1.6.0_21 – dogbane

0

se è necessario utilizzare un cast, è possibile eliminare l'avviso con @SuppressWarnings

Object o = new Object(); 

    @SuppressWarnings("unchecked") 
    Outer<T>.Inner returnVal = (Outer.Inner) o; 

    return returnVal; 

Ma realizzare esiste l'avviso perché si sta facendo qualcosa di pericoloso. L'oggetto non può essere trasmesso a String. Ciò provocherà un'eccezione in fase di runtime.

E come The Gentleman Elite notato, si consiglia di contrassegnare interno come statica:

public static class Inner { 
+2

La soppressione di un avviso non elude l'errore di compilazione. –

+0

@ Marco ha detto "Se cambio il cast ... compila, anche se c'è un avviso" –

+1

@Mark ma fai apparire un buon punto, avrei dovuto togliere lo . Risolto ora. –

2

La cosa più divertente è che, a meno che non ci sia qualcosa che mi manca di generici Java, sia

return (Outer<T>.Inner) o; 

E

return (Outer.Inner) o; 

Sia compilare lo stesso bytecode.

Il problema per la prima riga si verifica durante l'analisi, ovvero javac ed Eclipse non utilizzano lo stesso parser per il codice sorgente Java. Dovresti fare una domanda su quali differenze ci siano tra java parser e javac di Eclipse JDT. (O pubblica un bug su Eclipse).

Se ti ostini a mantenere quel comportamento (vorrei suggerire refactoring Inner ad una classe interna statica), è possibile utilizzare @SuppressWarning con un'assegnazione di campo (al fine di limitare il @SuppressWarning al più piccolo campo di applicazione possibile).

@SuppressWarnings({"rawtypes","unchecked"}) 
Outer<T>.Inner casted = (Outer.Inner)o; 
return casted; 

EDIT: OK, credo ho preso - JDT di Eclipse analizza il codice Java prima di passarlo al compilatore - e il loro parser in grado di dare un senso a un cast, mentre (almeno il vostro e il mio versione di) javac non può. (E dopo questo, Eclipse passa direttamente il codice analizzato alla compilazione). Prima di presentare un bug, guarda come si comporta l'ultima versione di Java 6.

+0

Il bytecode è lo stesso a causa della "cancellazione del tipo" in cui tutte le informazioni sui generici vengono rimosse al momento della compilazione. – dogbane

+0

Modificata questa risposta, sto attualmente scaricando la versione JDK più recente di Sun per vedere cosa succede. –

+0

quindi mi stai dicendo che non hai mai compilato un Sun JDK? –

0

(Dare un'altra risposta perché la mia precedente è troppo rumoroso.)

Ci sono due grandi passi da fare al fine di compilare il codice sorgente Java in bytecode:

  1. Il codice sorgente deve essere analizzato in un albero di sintassi.
  2. L'albero di sintassi viene utilizzato per generate bytecode.

Quando javac compila, utilizza il proprio parser e generatore di codice (i cui dettagli di implementazione dipendono dal JDK che si sta utilizzando).

Quando JDT di Eclipse viene compilato, utilizza il proprio parser di codice e dopo questo ... Non lo so. Il punto che voglio fare è che, in un modo o nell'altro, "bypassano" alcuni dei parser di javac. (Ad esempio, potrebbero passare a file java modificati javac che sostituiscono tutti i cast di classi generiche con le classi raw).

Il mio punto è - alla fine, è un bug nella parte di javac che analizza il codice sorgente java. (Non vi è alcun motivo per cui un tale costrutto non sia consentito nelle specifiche del linguaggio.)

Per ovviare a tale errore, è possibile: * Modificare il progetto dell'applicazione per evitarlo completamente. * Ogni volta che devi fare questo cast, tutti mettono un tipo grezzo e un'annotazione @SuppressWarnings invece del cast naturale.