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?
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 thefinal
field may not be observed, since uses of thatfinal
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");
}