事象
文字列定数(public static final String)ばかりが定義されたクラスConst.javaと、その定数を利用するMain.javaがあるとします。
public class Const {
public static final String VALUE = "BEFORE";
}
public class Main {
public static void main(String[] args) {
System.out.println(Const.VALUE);
}
}
このふたつのjavaファイルをコンパイルし、実行すると、文字列定数Const.VALUEの値BEFOREが出力されます。
C:\qiita> javac Const.java Main.java
C:\qiita> java Main
ここで、文字列定数Const.VALUEの値が変更になったとします。
public class Const {
public static final String VALUE = "AFTER";
}
Const.javaを編集後、Const.javaだけを再コンパイルし、ふたたびMainを実行したところ、画面にはConst.VALUEの現在の値であるAFTERではなく、BEFOREが表示されました。
C:\qiita> del Const.class
C:\qiita> javac Const.java
C:\qiita> java Main
BEFORE
調査
javapコマンドでMain.classを逆アセンブルすると、Main.classにはBEFOREという値が埋め込まれていることがわかります。
C:\qiita> javap -c Main.class
Compiled from "Main.java"
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String BEFORE
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
コンパイラはコンパイル時にMain.java内のConst.VALUEをBEFOREという値に置き換えています。Main.classはConst.classをすでに参照していないため、Const.VALUEの値が変わっても、その変更がMain.classにまで伝播しませんでした。
ちなみにMain.classはConst.classを参照していないため、Const.classを削除してしまっても、Main.classは正常に動作します。
C:\qiita> del Const.class
C:\qiita> java Main
BEFORE
原因
なぜ文字列定数が埋め込まれてしまったのかというと、これはJavaの仕様に起因します。"Java Language Specs - 13.1. The Form of a Binary" には次の通り記述されています。
A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V denoted by the constant variable's initializer.
If such a field is static, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field. Such a field must always appear to have been initialized (§12.4.2); the default initial value for the field (if different than V) must never be observed.
"Java Language Specs - 13.1. The Form of a Binary"には以下のとおり記述されており、文字列定数(public static final String)も埋め込みの対象であることがわかります。
A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28).
対策
文字列定数だけでなく、public static finalとして宣言された定数の値が変更になった場合、関連するソースコードはすべてコンパイルしなおすべきです。
また上記のような問題をはらんでいることからもpublic static final Stringの乱用は危険です。定数を表現したい場合はenumを利用することも検討しましょう。