Javaの定数がインライン化される理由

Javaの定数がインライン化される理由は、実行時に switch 文のラベルの定数値に重複がないことをコンパイル時に保証することにある。
次のリンク先には Javaの定数がインライン化される理由について書かれている。

トップ

同じことが書籍『Java言語仕様 第3版』にも次のように記されている。

(定数のインライン化が必要な理由の1つとして,switch 文ではそれぞれの case が定数を要求し,こういった定数値には重複が許されていないからである。コンパイラコンパイル時に switch 文中の重複した定数値の有無をチェックする。 class ファイル形式は case の値に対してシンボル・リンクを行わない。)

Java言語仕様 第3版 — P.309 13.4.9 final フィールドと定数
よって、次の3つのファイルがあったとき、 Main.javaコンパイルエラーとなる。

// A.java
class A {
    public static final int THREE = 3;
}
// B.java
class B {
    public static final int THREE = 3;
}
// Main.java
public class Main {
    public static void main(String[] args) {
        switch(3) {
        case A.THREE:
            break;
        case B.THREE:  // コンパイルエラー
            break;
        }
    }
}

単純に定数値の重複チェックだけなら、インライン化しなくてもコンパイル時にチェックすることができるが、
実行時においてラベルの定数値に重複がないことをコンパイル時に保証するには、インライン化するしかない。

逆に言えば、もし、シンボル・リンクにしてしまうと、コンパイル時に定数値が重複してなかったとしても、
switch 文を含むコードをコンパイルした後、定数を定義しているクラスで定数値を変更した場合、
実行時にはラベルの値が重複してしまう可能性が出てくる。

その他、インライン化の正当性を説明する記事として次のものも見つかった。

Javaの定数(static final)のインライン化について - 自分の歩いた道に落ちてるコード

このリンク先で言われる「バイナリ互換性の問題」とは、static final 定数を定義するクラスを変更したら、
それを参照しているクラスも再コンパイルを必要とされる問題だと思われる。
この問題については以下でも言及されている。

http://blog.sedays.com/1008/0052_17.php

これらのどの記事も static final 定数について言及しているが、
インライン化は何も static 定数だけに限らない。

実際、次のような非static定数を持つコードで試したところ、
value の初期値の変更を Main.class に反映させるには
Main.java のリコンパイルが必要となった。

// A.java
public class A {
    public final int value = 1;
}
// Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println((new A()).value);
    }
}

ちなみに

public static final int FOUR = 4;

は定数だが

public static final int FOUR = new Integer(4).intValue();

は定数ではない。
したがって後者はswitch文のラベルには使えない。

どうしても final 定数をインライン化したくなければ
final 定数をインライン化しないで使用する - happynowの日記
のような対処も考えられる。