更改"静态最终"字段的值

Use*_*291 1 java instrumentation bytecode java-bytecode-asm soot

比方说我有一个

class Foo(){
    public final static int bar = -1;
}
Run Code Online (Sandbox Code Playgroud)

反汇编的字节码看起来像这样

super public class Foo
    version 51:0
{

public static final Field bar:I = int -1;

public Method "<init>":"()V"
    stack 1 locals 1
{
        aload_0;
        invokespecial   Method java/lang/Object."<init>":"()V";
        return;

}

} // end Class Foo
Run Code Online (Sandbox Code Playgroud)

是的,这让我感到惊讶.我本来期望有一个<clinit>包含赋值的方法bar,然后我可以替换它.(当我删除final修饰符时会发生这种情况.)

如何更改final字段的值?我该怎么做?

Ste*_*n C 5

你的期望是不正确的.static final int用整数文字初始化的A 将是编译时常量.编译时常量由字节码编译器内联.

无法在运行时更改值或使用字节码修改.字节码编译器所做的内联无法解开.重新编译类及其依赖类是更改编译时常量值的唯一可行方法.

请注意,这不仅仅是Java编译器实现的一个不方便的工件.编译时常量的这种处理是由JLS强制执行的.例如,JLS 17.5.3说明了尝试final使用反射更改编译时常量:

"如果final在字段声明中将字段初始化为常量表达式(第15.28节),则final可能无法观察到对字段的更改,因为final在编译时将该字段的使用替换为常量表达式的值."

换句话说,对更改的反射API调用Foo.bar似乎成功,但内联的实际值不会更改.事实上,唯一可能看到更新值的代码是Foo.bar使用反射读取的代码!

一种可能的解决方法是以一种使其不是编译时常量的方式声明常量.例如:

class Foo() {
    public final static int bar = Integer.parseInt("-1");
}
Run Code Online (Sandbox Code Playgroud)