2015-09-28 37 views
7

Quando disassembro un enum con javap, gli argomenti impliciti del costruttore dell'enum sembrano mancare e non riesco a capire perché.Enum disassemblato con javap non mostra gli argomenti del costruttore

Ecco un enum:

enum Foo { X } 

compilo e smontare questo (su Java 8u60) con questo comando:

javac Foo.java && javap -c -p Foo 

Ed ecco l'output ottengo:

final class Foo extends java.lang.Enum<Foo> { 
    public static final Foo X; 

    private static final Foo[] $VALUES; 

    public static Foo[] values(); 
    Code: 
     0: getstatic  #1     // Field $VALUES:[LFoo; 
     3: invokevirtual #2     // Method "[LFoo;".clone:()Ljava/lang/Object; 
     6: checkcast  #3     // class "[LFoo;" 
     9: areturn 

    public static Foo valueOf(java.lang.String); 
    Code: 
     0: ldc   #4     // class Foo 
     2: aload_0 
     3: invokestatic #5     // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 
     6: checkcast  #4     // class Foo 
     9: areturn 

    private Foo(); // <--- here 
    Code: 
     0: aload_0 
     1: aload_1 
     2: iload_2 
     3: invokespecial #6     // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V 
     6: return 

    static {}; 
    Code: 
     0: new   #4     // class Foo 
     3: dup 
     4: ldc   #7     // String X 
     6: iconst_0 
     7: invokespecial #8     // Method "<init>":(Ljava/lang/String;I)V 
     10: putstatic  #9     // Field X:LFoo; 
     13: iconst_1 
     14: anewarray  #4     // class Foo 
     17: dup 
     18: iconst_0 
     19: getstatic  #9     // Field X:LFoo; 
     22: aastore 
     23: putstatic  #1     // Field $VALUES:[LFoo; 
     26: return 
} 

La mia confusione è con il costruttore privato utilizzato per istanziare ogni costante enum. Lo smontaggio mostra che non richiede argomenti (private Foo();), ma sicuramente accetta argomenti. Ad esempio, è possibile visualizzare le istruzioni load che leggono il nome e l'ordinale della costante enum passato, nonché il puntatore this e passarli a the superclass constructor, che li richiede. Il codice nel blocco di inizializzazione statico mostra anche che inserisce tali argomenti nello stack prima di chiamare il costruttore.

Ora mi avrebbe assunto questo era solo un oscuro bug in javap, ma quando compilo esattamente la stessa enum con il compilatore di Eclipse e smontare che l'utilizzo javap, il costruttore è esattamente la stessa, tranne gli argomenti sono mostrato:

final class Foo extends java.lang.Enum<Foo> { 
    public static final Foo X; 

    private static final Foo[] ENUM$VALUES; 

    static {}; 
    Code: 
     0: new   #1     // class Foo 
     3: dup 
     4: ldc   #12     // String X 
     6: iconst_0 
     7: invokespecial #13     // Method "<init>":(Ljava/lang/String;I)V 
     10: putstatic  #17     // Field X:LFoo; 
     13: iconst_1 
     14: anewarray  #1     // class Foo 
     17: dup 
     18: iconst_0 
     19: getstatic  #17     // Field X:LFoo; 
     22: aastore 
     23: putstatic  #19     // Field ENUM$VALUES:[LFoo; 
     26: return 

    private Foo(java.lang.String, int); // <--- here 
    Code: 
     0: aload_0 
     1: aload_1 
     2: iload_2 
     3: invokespecial #23     // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V 
     6: return 

    public static Foo[] values(); 
    Code: 
     0: getstatic  #19     // Field ENUM$VALUES:[LFoo; 
     3: dup 
     4: astore_0 
     5: iconst_0 
     6: aload_0 
     7: arraylength 
     8: dup 
     9: istore_1 
     10: anewarray  #1     // class Foo 
     13: dup 
     14: astore_2 
     15: iconst_0 
     16: iload_1 
     17: invokestatic #27     // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V 
     20: aload_2 
     21: areturn 

    public static Foo valueOf(java.lang.String); 
    Code: 
     0: ldc   #1     // class Foo 
     2: aload_0 
     3: invokestatic #35     // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 
     6: checkcast  #1     // class Foo 
     9: areturn 
} 

la mia domanda è: che cosa fisicamente è differente tra un enum javac-compilato e un enum Eclipse-compilati che provoca javap per non mostrare gli argomenti del costruttore per l'enum javac-compilato? E questa differenza è un bug (in javap, in javac o in Eclipse)?

+0

A ipotesi, l'impostazione del flag '-g' (controlla la generazione delle informazioni di debug). – CPerkins

+0

@CPerkins Buone ipotesi ma ho confrontato '-g' (genera tutte le informazioni di debug) e' -g: none' (non genera informazioni di debug) e sembra non fare alcuna differenza. – Boann

+0

Hai provato il flag '-v' (verbose)? Dovrebbe almeno mostrarti il ​​descrittore del costruttore. La mia ipotesi è che 'javac' segna i primi parametri' MANDATO', il che potrebbe far sì che 'javap' li ometti. – Clashsoft

risposta

3

I parametri e il tipo di ritorno di un metodo all'interno di un file di classe sono descritti da method descriptor.

Con l'introduzione di generici in 1.5. ulteriori informazioni sono state introdotte nel formato di file di classe, il method signature.

Il "descrittore del metodo" viene utilizzato per descrivere il metodo dopo la cancellazione del tipo, la "firma del metodo" contiene inoltre le informazioni di tipo generico.

Ora javap stampa la firma del metodo (che contiene ulteriori informazioni) e quando è impostato il flag -v, stampa anche il descrittore.

Questo rivela che anche il costruttore della classe enum generata da javac ha un descrittore di metodo con i tipi di parametro String e int. Ora è anche chiaro il motivo per cui funzionano sia il codice generato da Elipse che da javac. Entrambi chiamano il costruttore privato con argomenti String e int.

Che cosa deve ancora essere spiegato: perché lo javac crea una firma che differisce dal descrittore del tutto, non sono coinvolti generici?

In ogni caso, il comportamento del javac per quanto riguarda il costruttore enum ha causato other troubles ed una segnalazione di bug per javac era filed:

Non v'è alcuna necessità di costruttore di una dichiarazione enum per avere un attributo Firma la memorizzazione di un metodo firma se 1) il costruttore non è generico e 2) i suoi tipi di parametri formali non sono né tipi parametrizzati né variabili di tipo. È un bug se javac si aspetta un attributo Signature per il costruttore scritto sopra.

I seguenti commenti e la classificazione del caso suggeriscono che questo è un bug reale in javac.

+0

Grazie, questo spiega la differenza. Si scopre che javac include * esplicitamente * parametri enum constructor definiti nella firma, non solo i due impliciti per il nome costante e ordinale. Non sono convinto che si tratti di un bug in javac, ma più di una carenza nelle specifiche VM. Le regole della specifica VM per l'attributo signature, per quando deve essere presente e cosa si suppone contenga, sembrano essere definite in modo non chiaro. – Boann

+0

Un problema simile è con i costruttori delle classi interne, che hanno un parametro nascosto usato per passare il riferimento alla classe esterna. Ad esempio: 'class Outer {class Inner {Inner() {}}}' - il costruttore appare nello smontaggio come: 'Outer $ Inner (Outer);'. Ma se il costruttore è reso * generico *: 'class Outer {class Inner { Inner() {}}}', ottiene un attributo signature nel file class e javap lo disassembla come ' Outer $ Inner();' - * il parametro è improvvisamente invisibile *, e questo vale sia per javac che per ECJ. Cose strane! Almeno l'opzione '-v' rivela la verità! – Boann