14

次のコードはNullPointerExceptionin main で失敗します(map==null)。この問題は、2001 以上の Enum 定数を定義した場合にのみ発生し、2000 は正常に動作します。

静的コード ブロックが実行されないのはなぜですか?

コンパイラまたは JVM のサイレント リミット (警告なし、エラーなし) に達していますか?

コンパイルされたクラス ファイルが 172KB を超えています。

import java.util.HashMap;

public enum EnumTest {
    E(1),E(2),...,E(2001);

    private static HashMap<Integer, EnumTest>   map = new HashMap<Integer, EnumTest>();

    static {

        for ( EnumTest f : EnumTest.values() ) {
            map.put( (int) f.id, f );
        }
    }
    short id;

    private EnumTest(int id) {
        this.id = (short) id;
    };

    public short getId() {
        return id;
    }

    public static final EnumTest fromInt(int id) {
        EnumTest e = map.get( id );
        if ( e != null ) {
            return e;
        }
        throw new IllegalArgumentException( "" + id );
    }

    public static void main(String[] args) {
        System.out.println( "size:" + map.size() );
    }
}

実行時環境:

java version "1.7.0_01"
Java(TM) SE Runtime Environment (build 1.7.0_01-b08)
Java HotSpot(TM) 64-Bit Server VM (build 21.1-b02, mixed mode)

次の場合にも発生します。

java version "1.6.0_32" Java(TM) SE Runtime Environment (build
1.6.0_32-b05) Java HotSpot(TM) Client VM (build 20.7-b02, mixed mode, sharing)
4

2 に答える 2

13

これらの種類の問題は、一部の (多くの場合、コンパイラによって生成された) 初期化子コードが 65536 バイトのバイト コードを超えるという事実から発生します。1 つのメソッドに、実行するバイトコードをそのバイト数以上含めることはできません (クラス ファイル形式の制限のため)。

このような問題の一般的な原因は、次のような大きな配列です。

byte someBytes = { 1, 2, 3, ..., someBigValue };

ここでの問題は、そのようなフィールドが実際には、生成された初期化子 (コンストラクターまたは静的初期化子)でいくつかのBigValue割り当てステートメントで初期化されることです。

列挙値は、実際には同様の方法で初期化されます。

次の列挙型クラスがあるとします。

public enum Foo {
  CONSTANT(1);

  private Foo(int i) {
  }
}

の出力をjavap -v見ると、次のコード ブロックが表示されます。

  static {};
    flags: ACC_STATIC
    Code:
      stack=5, locals=0, args_size=0
         0: new           #4                  // class Foo
         3: dup
         4: ldc           #7                  // String CONSTANT
         6: iconst_0
         7: iconst_1
         8: invokespecial #8                  // Method "<init>":(Ljava/lang/String;II)V
        11: putstatic     #9                  // Field CONSTANT:LFoo;
        14: iconst_1
        15: anewarray     #4                  // class Foo
        18: dup
        19: iconst_0
        20: getstatic     #9                  // Field CONSTANT:LFoo;
        23: aastore
        24: putstatic     #1                  // Field $VALUES:[LFoo;
        27: return

ご覧のとおりCONSTANT、正しい値でインスタンス化を処理するバイトコード操作は非常に多くあります。このような列挙値が多数ある場合、その静的初期化子ブロックのサイズはコードの 64k バイトを簡単に超えてしまい、クラスがコンパイルできなくなります。

可能な回避策は、引数の数を減らすことによって初期化コードのサイズを減らすことです (たとえば、引数を使用する代わりに、列挙値のインデックスに基づいて渡される数を計算することによって)。これにより、これをさらに拡張するのに十分な小刻みな余裕が得られるかもしれません.

または、共通のインターフェイスを実装することで、列挙型を接続されたいくつかの列挙型に分割することもできます。列挙型は、領域/意図/カテゴリ/... でグループ化できます。

public interface MessageType {
  int getId();
}

public enum ConnectionMessage implements MessageType {
  INIT_CONNECTION(1),
  LOGIN(2),
  LOGOUT(3),
  CLOSE_CONNECTION(4);

  // getId code, constructor, ...
}

public enum FrobnicationMessage implements MessageType {
  FROBNICATE_FOO(5),
  FROBNICATE_BAR(6),
  DEFROB_FOO(7),
  DEFROB_BAR(8),
  ...

  // getId code, constructor, ...
}

列挙型の値は、純粋な値の保持者だけでなく、コードのどこかで実際に参照されていると想定しています。それらが値のみを保持し、個々の値がコード内で異なる方法で処理されない場合は、データ項目ごとに 1 回インスタンス化された単一のクラスに置き換えます。中央リソースに格納するのがおそらく最良のアプローチです。

于 2012-08-09T12:33:25.860 に答える
6

表示されるのはコンパイル時のエラーだと思います

error: code too large

コンパイラのバージョンにバグがあり、これが表示されない可能性があります。

2500 個の列挙値を作成すると、このエラーで失敗しますが、2400 個の列挙値では正しく実行されます。

メソッドのバイト コードには 64 KB の制限があり、列挙型は静的初期化ブロックの 1 つのメソッドで初期化されます。

問題は、多くのバイト コード命令がバイト オフセットを 16 ビット値として使用し、メソッド全体にこの制限を課すことです (メソッドの最後にそのような命令がない場合でも)。

静的初期化ブロックを複数のjavacサブメソッドに分割するほどスマートではありませんが、何千ものenums提案があれば、別の方法で必要なことを行うことができます。

于 2012-08-09T12:35:27.073 に答える