Search code examples
javabytecodeinstrumentationjava-bytecode-asmsoot

Changing the value of a "static final" field


Let's say I have a

class Foo(){
    public final static int bar = -1;
}

disassembled bytecode will look something like this

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

And yes, this surprised me. I would have expected there to be a <clinit> method containing an assignment to bar that I could then replace. (Which is indeed what happens when I remove the final modifier.)

How do I change the value for a final field? What do I hook into?


Solution

  • Your expectation is incorrect. A static final int which is initialized with an integer literal will be a compile-time constant. Compile-time constants get inlined by the bytecode compiler.

    It will not be possible to change the value at runtime, or using bytecode modification. The inlining that the bytecode compiler did can't be unwound. Recompilation of the class and its dependent classes is the only tractable way to change the value of a compile-time constant.

    Note this is not just an inconvenient artifact of the Java compiler implementation. This handling of compile-time constants is mandated by the JLS. For example, JLS 17.5.3 says this about trying to change a compile-time constant final using reflection:

    "If a final field is initialized to a constant expression (§15.28) in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the value of the constant expression."

    In other words, a reflective API call to change Foo.bar may appear to succeed, but the actual value that has been inlined doesn't change. In fact, the only code that is likely to see the updated value is code that reads Foo.bar using reflection!

    One possible workaround would be to declare the constant in a way that makes it not a compile-time constant. For example:

    class Foo() {
        public final static int bar = Integer.parseInt("-1");
    }