更改静态变量可以使用原始包装,但不能使用原始类型

时间:2014-04-24 15:56:15

标签: java class reflection 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一起使用。

3 个答案:

答案 0 :(得分:6)

来自jls-4.12.4

  

基本类型或类型String的变量,是最终的并使用编译时常量表达式(§15.28)初始化,称为constant variable

13.1节也说(强调我的)

  

3 .. 对常量变量字段(第4.12.4节)的引用在编译时解析为表示为的常量值。二进制文件中的代码中不应存在对此类字段的引用(包含该字段的类或接口除外,该字段将具有初始化它的代码)。这样的字段必须总是看似已经初始化(§12.4.2);不得观察此类字段类型的默认初始值。

这意味着来自常量变量编译时常量表达式将被编译器直接放入代码中(它将被内联),而不是在运行时从最终引用中读取。

例如,如果您从main

执行Bar方法
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的值也不会影响main类中Bar方法中打印的值。

您无法更改该机制。


可能的方法是使用在运行时创建的值初始化最终字段(它们在编译时不存在,这将阻止它们成为编译时常量表达式)。而不是

public static final String x = "foo";

尝试

public static final String x = new String("foo");

或者在原始类型的情况下使用取消装箱而不是

public static final int x = 42;

使用

public static final int x = new Integer(42);

答案 1 :(得分:1)

  1. 原始类型内联。

  2. 事实上,即使是非原始常量,当导入到其他类时,也会被复制,并且导入会被遗忘。所以它也不会起作用。仅对于常量缓存(如字符串池)和Integer(Integer.valueOf(13))缓存,您可以覆盖它们的值。

答案 2 :(得分:0)

这是因为在编译期间内联或String类型的静态final字段被内联。在编译和反编译之后,main方法看起来像这样。

  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替换为实际值。