我遇到了必须更改java常量的情况。
我有下面的代码工作
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Main {
public static final Integer FLAG = 44;
static void setFinalStatic(Class<?> clazz, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
Field modifiers = field.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
public static void main(String... args) throws Exception {
System.out.printf("Everything is %s%n", FLAG);
setFinalStatic(Main.class, "FLAG", 33);
System.out.printf("Everything is %s%n", FLAG);
}
}
如果我在上面运行,我会得到以下输出:
Everything is 44
Everything is 33
但如果我将FLAG
变量更改为int,即
public static final int FLAG = 44;
它不起作用。输出为:
Everything is 44
Everything is 44
有没有其他方法可以使它与Primitive
类型int
一起工作。
来自jls-4.12.4
基元类型或String类型的变量是最终变量,并用编译时常数表达式(§15.28)初始化,称为
constant variable
。
同样,第13.1节说(强调矿)
3.对常量变量字段(§4.12.4)的引用在编译时解析为表示为的常数值。二进制文件中的代码中不应存在对此类字段的引用(包含该字段的类或接口中除外,该类或接口将具有初始化该字段的代码)。此类字段必须始终显示为已初始化(§12.4.2);决不能观察到此类字段类型的默认初始值。
这意味着,来自常量变量的编译时常量表达式将由编译器直接放入代码中(它将内联),而不是在运行时从最终引用中读取。
例如,如果从Bar
类执行main
方法
class Foo{
static{
System.out.println("test if class will be loaded");
}
public static final int x = 42;
}
class Bar{
public static void main(String [] args){
System.out.println(Foo.x);
}
}
您将看不到来自Foo
类的静态块的输出,这意味着Foo
类尚未加载,也意味着Foo.x
的值不是来自这个类。事实上Bar
是这样编译的
class Bar{
public static void main(String [] args){
System.out.println(42); // reference Foo.x will be removed by compiler
// and replaced with actual value because
// compiler assumes that value can't/shouldn't
// change at runtime
}
}
因此,即使在运行时更改Foo.x
的值,也不会影响Bar
类中main
方法打印的值。
你无法改变这种机制。
可能的方法是用运行时创建的值初始化最终字段(它们在编译时不存在,这将阻止它们成为编译时常量表达式)。所以不是
public static final String x = "foo";
尝试
public static final String x = new String("foo");
或者在基元类型的情况下,使用Unboxing like而不是
public static final int x = 42;
使用
public static final int x = new Integer(42);
-
基元类型被内联。
-
事实上,即使是非基元常量,当导入到其他类中时,也会被复制,并且忘记导入。因此,它也不会起作用。只有对于常量缓存,如字符串池和Integer(Integer.valueOf(13))缓存,您可以覆盖它们的值。
这是因为基元或字符串类型的静态最终字段在编译时内联。在编译和反编译之后,主要方法看起来是这样的
public static void main(String... args) throws Exception {
System.out.printf("Everything is %s%n", 44);
setFinalStatic(Main.class, "FLAG", 33);
System.out.printf("Everything is %s%n", 44);
}
因为FLAG在编译时会被实际值所取代。