web-dev-qa-db-ja.com

javacによる静的最終変数のインライン化を無効にすることは可能ですか?

Java静的コンパイラ(javac)は、いくつかの静的最終変数をインライン化し、値を定数プールに直接もたらします。次の例を検討してください。クラスAはいくつかの定数(パブリック静的最終変数)を定義します。

public class A {
    public static final int INT_VALUE = 1000;
    public static final String STRING_VALUE = "foo";
}

クラスBは次の定数を使用します。

public class B {
    public static void main(String[] args) {
        int i = A.INT_VALUE;
        System.out.println(i);
        String s = A.STRING_VALUE;
        System.out.println(s);
    }
}

クラスBをコンパイルすると、javacはクラスAからこれらの定数の値を取得し、これらの値をB.classにインライン化します。その結果、コンパイル時にクラスAに必要だった依存関係Bがバイトコードから消去されます。これらの定数の値をベイクしているため、これはかなり特殊な動作ですコンパイル時。そして、これはJITコンパイラーが実行時に実行できる最も簡単なことの1つだと思うでしょう。

Javacのこのインライン化動作を無効にする方法または隠しコンパイラオプションはありますか?背景として、依存関係の目的でバイトコード分析を行うことを検討しています。これは、バイトコード分析がコンパイル時の依存関係を検出できない数少ないケースの1つです。ありがとう!

編集:通常、すべてのソース(定数を定義するサードパーティライブラリなど)を制御するわけではないため、これは厄介な問題です。 sing定数の観点からこれらの依存関係を検出することに関心があります。 ses定数であるコードから参照が消去されるため、ソースコード分析を行う以外に、それらを検出する簡単な方法はありません。

54
sjlee

Javaパズル(ジョシュアブロック)のアイテム93は、最終値が定数と見なされないようにすることで、これを回避できると述べています。例:

public class A {
  public static final int INT_VALUE = Integer.valueOf(1000).intValue();
  public static final String STRING_VALUE = "foo".toString();
}

もちろん、定数を定義するコードにアクセスできない場合、これは関係ありません。

44
DJClayworth

私はそうは思わない。最も簡単な回避策は、これらをフィールドではなくプロパティとして公開することです。

public class A {
    private static final int INT_VALUE = 1000;
    private static final String STRING_VALUE = "foo";

    public static int getIntValue() {
        return INT_VALUE;
    }
    public static String getStringValue() {
        return STRING_VALUE;
    }
}

場合によっては、値の使用にインライン化が不可欠であることを忘れないでください。たとえば、スイッチブロックのケースとしてINT_VALUEを使用する場合、has to定数値として指定します。

13
Jon Skeet

インライン化を停止するには、値を非コンパイル時定数(JLS用語)にする必要があります。これは、関数を使用せずに、イニシャライザー式でnullを使用して、最小限のバイトコードを作成することなく実行できます。

public static final int INT_VALUE = null!=null?0: 1000;

コード生成では非常にリテラルですが、javacはこれを最適化して、即時整数のプッシュとそれに続く静的イニシャライザーの静的フィールドへのストアにする必要があります。

JLS 13.4.9 この問題を扱います。値が変更される可能性がある場合は、基本的にコンパイル時定数を回避することをお勧めします。

(定数のインライン化が必要な理由の1つは、switchステートメントではそれぞれの場合に定数が必要であり、そのような定数値が2つ同じであってはならないためです。コンパイラーはコンパイル時にswitchステートメントで重複する定数値をチェックします。クラスファイル形式はチェックしません。ケース値のシンボリックリンケージを実行します。)

広く配布されているコードの「不定定数」の問題を回避する最善の方法は、コンパイル時の定数として、実際には変更される可能性が低い値のみを宣言することです。真の数学定数を除いて、静的および最終として宣言されているクラス変数をソースコードで非常に控えめに使用することをお勧めします。 finalの読み取り専用の性質が必要な場合は、プライベート静的変数とその値を取得するための適切なアクセサーメソッドを宣言することをお勧めします。したがって、次のことをお勧めします。

private static int N;
public static int getN() { return N; }

のではなく:

public static final int N = ...;

問題はありません:

public static int N = ...;

nを読み取り専用にする必要がない場合。

7
Mark Peters

これは深刻なバグだと思います。 JavaはC/C++ではありません。「一度コンパイルして、どこでも実行する」という原則があります(またはありません)。

この場合、クラスAが変更されたとき。 A.CONST_VALUEを参照するクラスはすべて再コンパイルする必要があり、クラスAが変更されたかどうかはほとんどわかりません。

2
neoedmund

クラスAを次のように書き直します。

public class A {
    public static final int INT_VALUE;
    public static final String STRING_VALUE;

    static {
        INT_VALUE = 1000;
        STRING_VALUE = "foo";
    }
}
2
rveach

jmake は、Javaファイル間の依存関係を追跡し、必要なファイルの最小セットを段階的にコンパイルするという仕事全体を実行すると主張するオープンソースプロジェクトです。プロジェクト全体の再コンパイルが必要になる場合もありますが、静的最終定数への変更を正しく処理するため。クラスファイルよりも細かい粒度で変更を処理することもできます。たとえば、メソッドCm()の署名が変更された場合は、再コンパイルのみが行われます。 Cを使用するすべてのクラスではなく、実際にm()に依存するクラス。

免責事項:私はjmakeを使用した経験がありません。

1
mhagger

最近、同様の issue に遭遇しました。前述のように、このようなインライン化は、コンパイル時以外の式を使用して回避できます。

_public final class A {

    public static final int INT_VALUE = constOf(1000);
    public static final String STRING_VALUE = constOf("foo");

}
_

ここで、constOfメソッドファミリーは単に次のとおりです。

_// @formatter:off
public static boolean constOf(final boolean value) { return value; }
public static byte constOf(final byte value) { return value; }
public static short constOf(final short value) { return value; }
public static int constOf(final int value) { return value; }
public static long constOf(final long value) { return value; }
public static float constOf(final float value) { return value; }
public static double constOf(final double value) { return value; }
public static char constOf(final char value) { return value; }
public static <T> T constOf(final T value) { return value; }
// @formatter:on
_

これは、Integer.valueOf(1000).intValue()や_null!=null?0: 1000_などの他の提案よりも少し短いです。

0