Changing final fields? Really? Which may sound crazy at a first glance may be helpful in order to implement mocks or fix-up libraries that don't expose the state that you really wanted them to expose. And after all there is not always a fork me button available. But really: Final fields? Yes, indeed. You shall never forget: There is no spoon.
Let's consider the following data class Person that we want to hack.
Once a person was instantiated, it is not possible to change the value of the field name, is it?
The complete code looks like this and as promised, it will print the new name of the person to the console and the changed default name, too. But keep in mind: Don't do this at home!
Let's consider the following data class Person that we want to hack.
Once a person was instantiated, it is not possible to change the value of the field name, is it?
Reflection To The Rescue
Fortunately - or unfortunately - Java allows to access fields reflectively, and if (ab)used wisely, it is possible to change their value, too - even for final fields. Key is the method Field#setAccessible. It allows to circumvent visibility rules - which is step one - and interestingly it also implicitly allows change the value of instance fields reflectively, if they were marked as accessible.
Modifying static fields is a little trickier. Even if #setAccessible was invoked, the runtime virtual machine will throw an IllegalAccessException (which I would expect anyway) because one 'Can not set static final my.field.Type field' even though that was perfectly ok for instance fields. And since there is still no spoon - there is a way out. And again it's based on reflection, but this one's a little trickier. If we don't just set the field accessible but also change its modifiers to non-final, it's ok to alter the value of the field.
This hack will allow to change the value of static fields, too. That is, as long as they are not initialized with literals that will be inlined by the compiler. Those include number literals and string literals, which are compiled directly into the call site to save some computation cycles (yes, refering to String constants from other classes does not introduce a runtime dependency to those classes). Despite those cases, other common static field types like loggers or infamous singletons can be easily modified at runtime and even (re)set to null.The complete code looks like this and as promised, it will print the new name of the person to the console and the changed default name, too. But keep in mind: Don't do this at home!
11 comments:
Are there any issues with multi-threading? Usually, a final field does not have to be synchronized, so what do other threads see if you change its value?
Good point. Final fields may not be marked as volatile so the Java memory model does not guarantee that other thread see the changes. A quick test reveals that the spawned thread uses the updated change properly - at least on my current JVM. So no promises. After all I'd avoid that 'feature' anyway :-).
I think the point to remember is that this is for testing or for those truly absolutely positively no other way to accomplish something cases. I've done this myself but it always worries me nonetheless. There is no way to check type safety or have it participate in refactoring. The only way to validate is at runtime. In effect, it turns Java into a very tedious and painful to use dynamic language.
JVMs rely heavily on optimizations of "static final" fields, particularly for the code generated by Java "assert". Attempting to avoid the Field.set restriction on static final fields by using reflection on Field.modifiers sounds very dangerous to me.
I totally agree with your comments. This hack is more for fun than for serious problem solving.
I have read somewhere that the JVM knows how to de-optimize when reflection is used.
See also the ever-interesting Dr Cliff Click: http://www.azulsystems.com/blog/cliff/2011-10-17-writing-to-final-fields-via-reflection
Thank you for this article ! But in order to get the most from it , I would need some explanations concerning the following line from class Person
public static final String DEFAULT = new String("unnamed");
1. What does it mean for a field to be inlined ?
2. Why DEFAULT = new String("unnamed") is not inlined , and DEFAULT = "unnamed" would be ?
3. Why does this reflection mechanism work in the first case and doesn't work in the second ?
It can be even easier: http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection/31268945#31268945
Maybe if we add Modifier.VOLATILE?
Post a Comment