事象
文字列定数(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
を利用することも検討しましょう。