7

javap で列挙型を逆アセンブルすると、列挙型の暗黙的なコンストラクター引数が欠落しているように見え、その理由がわかりません。

列挙型は次のとおりです。

enum Foo { X }

これを(Java 8u60で)次のコマンドでコンパイルおよび逆アセンブルします。

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

そして、ここに私が得る出力があります:

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
}

私の混乱は、各列挙型定数をインスタンス化するために使用されるプライベートコンストラクターにあります。逆アセンブルは、引数をとらないことを示しています ( private Foo();) が、確かに引数をとります。たとえばload、渡された列挙定数の名前と序数、thisおよびポインターを読み取り、それらを必要とするスーパークラスのコンストラクターに渡す命令を確認できます。静的イニシャライザ ブロックのコードは、コンストラクタを呼び出す前にこれらの引数をスタックにプッシュすることも示しています。

今、これは javap のあいまいなバグだと思っていたでしょうが、Eclipse のコンパイラでまったく同じ列挙型をコンパイルし、javap を使用してそれを逆アセンブルすると、コンストラクターは引数表示されていることを除いてまったく同じです。

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
}

私の質問は、javac でコンパイルされた列挙型と、javac でコンパイルされた列挙型のコンストラクター引数を javap が表示しない原因となる Eclipse でコンパイルされた列挙型の物理的な違いは何ですか? そして、この違いは (javap、javac、または Eclipse の) バグですか?

4

1 に答える 1

3

クラス ファイル内のメソッドのパラメーターと戻り値の型は、メソッド記述子によって記述されます。

1.5 でジェネリックが導入されました。追加情報がクラス ファイル形式、メソッド シグネチャに導入されました。

「メソッド記述子」は、型消去後のメソッドを記述するために使用され、「メソッド署名」には、ジェネリック型情報が追加で含まれます。

javapメソッド シグネチャ (詳細情報を含む) を出力するようになりました。フラグが設定されている場合は、-v記述子も出力されます。

これは、生成された列挙型クラスのコンストラクターにも、パラメーター型およびjavacを持つメソッド記述子があることを示しています。これで、Elipse と javac の両方で生成されたコードが機能する理由も明らかになりました。どちらも引数とを指定してプライベート コンストラクターを呼び出します。StringintStringint

javacまだ説明する必要があるのは、記述子とはまったく異なる署名を作成するのはなぜですか- ジェネリックは関係ありませんか?

とにかく、javacenum コンストラクターに関する の動作は他の問題を引き起こし、バグレポートjavac提出されました:

1) コンストラクターがジェネリックではなく、2) 仮パラメーターの型がパラメーター化された型でも型変数でもない場合、enum 宣言のコンストラクターがメソッド シグネチャを格納する Signature 属性を持つ必要はありません。上記のコンストラクターに Signature 属性を javac が期待する場合、これはバグです。

次のコメントとケースの分類は、これが の実際のバグであることを示唆していjavacます。

于 2015-09-28T19:32:19.780 に答える