更改静态变量可以使用Primitive Wrapper,但不适用于Primitive类型



我遇到了必须更改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);
  1. 基元类型被内联。

  2. 事实上,即使是非基元常量,当导入到其他类中时,也会被复制,并且忘记导入。因此,它也不会起作用。只有对于常量缓存,如字符串池和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在编译时会被实际值所取代。

最新更新