2012-04-12 12 views
5

Stavo leggendo una discussione sui modelli C++ e sui generici C# e su come sono diversi dai generici cancellati dal tipo di Java. Ho letto una dichiarazione che diceva che Java utilizza ancora la fusione in fase di runtime, ad esempio quando si tratta di raccolte. Se questo è vero, non ne ero consapevole!Quando i generici di Java usano il cast in fase di runtime?

Diciamo che ho codice come:

ArrayList<SomeClass> list = new ArrayList<SomeClass>(); 
... 
SomeClass object = list.get(0); 

La mia domanda è. È efficacemente compilato per

ArrayList list = new ArrayList(); 
... 
SomeClass object = (SomeClass) list.get(0); 

Se sì, perché? Ho pensato che il fatto che la lista sia di tipo ArrayList<SomeClass> garantisce, al momento della compilazione e del tempo di esecuzione, che solo SomeClass sarà memorizzato all'interno di ArrayList? O puoi mai fare casting di tipo non sicuro per trasformare uno ArrayList<OtherClass> in uno ArrayList<SomeClass>?

Ci sono altre occasioni in cui il cast di tipo at-runtime viene eseguito in generici Java?

Infine, se si utilizza effettivamente il lancio in fase di esecuzione, ci sono occasioni in cui il JIT può elidere il controllo del tempo di esecuzione?

(Si prega di astenersi dal rispondere/commentare che le micro-ottimizzazioni non ne valgono la pena, l'ottimizzazione preventiva è la radice di tutto il male, ecc. Le vedo su altre domande simili.Questi punti sono ben compresi, ma non portano via il punto di cercare di capire come i generici cancellati dal tipo sono implementati sotto il cofano.)

+2

Hai un sacco di domande. Ma la risposta arriva a questo: Java Generics è stato messo in atto per migliorare il controllo degli errori in fase di compilazione – ControlAltDel

+0

Questo è praticamente coperto dalle varie informazioni e tutorial di Generics fornite da Oracle (Sun). Per mantenere la retrocompatibilità, i generici sono stati imbullati come funzionalità di compilazione. http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf –

+0

Le risposte di SLaks, Richante e Adriano mi stavano chiarendo. La linea di fondo è ciò che @ user1291492 menzionato, i generici ci sono principalmente per il controllo degli errori in fase di compilazione. È ancora un po 'oscuro ciò che fa il JIT. E mi sembra ancora che tu possa ottimizzare i controlli di runtime se la semantica di Java non consente conversioni non sicure (come 'ArrayList ' in 'ArrayList ') ma questo è un argomento per un'altra domanda. Scelgo la risposta di Richante come "corretta", per lo sforzo di compilazione e disassemblaggio (grazie!). – Max

risposta

5

Ecco un breve programma che ho scritto:

public class Test<T> { 

    public T contents; 

    public Test(T item) { 
    contents = item; 
    } 

    public static void main(String[] args) { 

    Test<String> t = new Test<String>("hello"); 
    System.out.println(t.contents); 

    } 

} 

provare a compilare con javac, e poi guardare il bytecode con javap -verbose.Ho selezionato alcune linee interessanti:

public java.lang.Object contents; 

Questo dovrebbe comparire prima della definizione del costruttore Test. Nel codice di esempio era di tipo T, ora è un oggetto. Questa è cancellazione.

Ora, guardando il metodo principale:

public static void main(java.lang.String[]); 
Code: 
    Stack=3, Locals=2, Args_size=1 
    0: new #3; //class Test 
    3: dup 
    4: ldC#4; //String hello 
    6: invokespecial #5; //Method "<init>":(Ljava/lang/Object;)V 
    9: astore_1 
    10: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream; 
    13: aload_1 
    14: getfield #2; //Field contents:Ljava/lang/Object; 
    17: checkcast #7; //class java/lang/String 
    20: invokevirtual #8; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
    23: return 

allora possiamo vedere il comando checkcast alla linea 17, poco prima della println - questo è dove Java getta dal Object al tipo generico cancellati - String

+0

Grazie. Buona spiegazione! –

5

Il tuo presupposto è corretto.

Il controllo di tipo è sempre necessario, perché si può scrivere il codice legale seguente:

ArrayList<X> good = new ArrayList<X>(); 
ArrayList q = x; 
ArrayList<Y> evil = (ArrayList<Y>)q; //Doesn't throw due to type erasure 
evil.add(new Y()); //this will actually succeed 
X boom = good.get(0); 

Il cast ArrayList-ArrayList<Y> sarà (sempre) dare un avvertimento getto incontrollato, ma sarà (anche sempre) riuscire a runtime.

+0

eh? cos'è questo? – ControlAltDel

+0

@ user1291492: Cosa? – SLaks

+0

Come si lascia un commento con solo 5 caratteri: mi fa fare almeno 15! – ControlAltDel

1

Ricordare che in fase di esecuzione, un ArrayList<String> è uguale a ArrayList ed è implementato con un sottostante Object[]. Il JIT è spesso abbastanza intelligente da eliminare i cast, sì, ma dal momento che i generici vengono cancellati in fase di runtime, i cast sono necessari.

0

Java sta facendo un ottimo lavoro per mantenere la compatibilità tra le sue versioni, il che significa che la sintassi di Java5 + può essere compilata in 1.4 classi compatibili. Immagino che sia parte del motivo per cui ArrayList fa non garanzia in fase di esecuzione che solo SomeClass sarà memorizzato all'interno di ArrayList.

E per vedere in cosa consiste veramente la compilazione del codice generico, utilizzare l'eccellente strumento JAD.

Il codice compilato in effetti in SomeClass object = (SomeClass) list.get(0);

1

ho pensato che il fatto che la lista è di tipo garanzie ArrayList, al compilare il tempo e il tempo di esecuzione, che solo SomeClass verrà memorizzato all'interno ArrayList? O si può mai fare un casting di tipo non sicuro per trasformare un ArrayList in un ArrayList?

Sì, le informazioni sul tipo vengono scartate in fase di esecuzione.

Significa che non è possibile capire se si dispone di un oggetto di tipo ArrayList<String> o un oggetto di tipo ArrayList<Long>. JVM non conosce gli argomenti di tipo effettivi per le istanze di classi generiche, quindi non hai i controlli di runtime (e vedrai gli errori solo quando consumerai quei dati).

Questo codice è valido:

public void enqueueItem(ArrayList<Integer> list, Integer item) { 
    List listAlias = list; 
    listAlias.add(x.toString()); 
} 

Non è un problema in fase di esecuzione, il tipo di controllo è ancora lì a risparmiare da qualsiasi problema e si può interoperabilità vecchio codice con il nuovo codice. Anche nell'ambiente .NET si ha lo stesso comportamento (IList<T> deriva da IList).

Quello che si paga è solo l'impatto sulle prestazioni di un cast (e questa è la grande differenza con l'implementazione generica di Java). Più o meno i generici sono un helper in fase di compilazione per rilevare errori e rendere il codice più leggibile. Per esempio, questo codice è abbastanza valido per il compilatore:

public ArrayList getCollection() { 
    return new ArrayList<Integer>(); 
} 

public void doStuff() { 
    ArrayList<String> list = getCollection(); 
    list.add("text"); 
} 

Ci sono altre occasioni in cui al-runtime di tipo colata viene fatto in generici Java?

Sì, ogni volta che li consumano (questo è il punto in cui si otterrà un errore se si fa qualcosa di brutto con i generici).

Infine, se la fusione in fase di esecuzione viene infatti utilizzato, ci sono occasioni in cui il JIT può elidere il controllo getto fase di esecuzione?

Io penso No, non sempre un cast, ma questo è un dettaglio di implementazione molto comunque tiene traccia degli argomenti di tipo per le sottoclassi, quindi se si deriva la propria classe da una classe generica, allora sarete salvati da questo numero .

Se sì, perché?

Tutto il codice esistente funzionerà con nuovi generici, anche se si modifica un'interfaccia. Significa che è possibile aggiornare in generici passo dopo passo. Non mi preoccupo dell'impatto sulle prestazioni (siamo sopravvissuti per molto tempo, è un problema ora?) Ma non mi piacciono tutti questi trucchi. Forse, dopo tutto, non possiamo avere la quercia piena e la moglie ha bevuto ...

Problemi correlati