【问题标题】:Changing static variable works Primitive Wrapper but not with Primitive type更改静态变量适用于 Primitive Wrapper,但不适用于 Primitive 类型
【发布时间】:2014-04-24 15:56:14
【问题描述】:

我有一种情况,我必须更改 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

【问题讨论】:

  • 变量要改为什么要声明final?删除final。
  • +1 对上述评论。简单的答案是 int 是不可变的,但我对 java 的了解不够深,无法自信地回答
  • 编译时常量是内联的。如果直到运行时才知道该值,则可以对其进行更改。注意:更改 final 变量肯定会引起混淆,因此您实际上不会在生产环境中这样做。
  • 应该不需要涉及modifiers的三行。
  • @mvmn 我无法删除它。我正在使用一个需要传递整数的库,但它只接受常量。这个值应该来自属性文件。

标签: java class reflection primitive


【解决方案1】:

来自jls-4.12.4

原始类型或字符串类型的变量,它是最终的并使用编译时常量表达式(§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 代替

public static final int x = 42;

使用

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

【讨论】:

  • 要查找的关键字是常量表达式和常量变量。
  • @SotiriosDelimanolis 你能看看编辑过的版本或我的答案吗?我绝对不是 Java 专家,所以我可能会犯一些我不知道的简单但关键的错误:/
  • 我不认为有什么问题。这是正确的答案。
  • @Pshemo 感谢您的回答。我试试看。
【解决方案2】:
  1. 原始类型被内联。

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

【讨论】:

    【解决方案3】:

    发生这种情况是因为原始类型或字符串类型的静态最终字段在编译期间内联。 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 在编译时被实际值替换。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-17
      • 1970-01-01
      • 2020-05-10
      • 2010-11-30
      • 1970-01-01
      • 2022-01-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多