Monday, January 21, 2013

Java Hacks - Changing Final Fields

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.
/**
* An immutable data class.
*/
class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
view raw Person.java hosted with ❤ by GitHub
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.
/**
* Force the field to accessible.
*/
protected static void makeModifiable(Field nameField) throws Exception {
nameField.setAccessible(true);
}
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.
/**
* Force the field to be modifiable and accessible.
*/
protected static void makeModifiable(Field nameField) throws Exception {
nameField.setAccessible(true);
int modifiers = nameField.getModifiers();
Field modifierField = nameField.getClass().getDeclaredField("modifiers");
modifiers = modifiers & ~Modifier.FINAL;
modifierField.setAccessible(true);
modifierField.setInt(nameField, modifiers);
}
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.
public class Doh {
public static void main(String[] args) throws Exception {
Person p = new Person("Name");
setName(p, "New Name");
setDefault("Bob");
System.out.println(p.getName());
System.out.println(Person.DEFAULT);
}
/**
* Allows to change the state of an immutable instance. Huh?!?
*/
private static void setName(Person p, String newName) throws Exception {
Field nameField = p.getClass().getDeclaredField("name");
setValue(p, nameField, newName);
}
/**
* Allows to change the state of final statics. Huh?!?
*/
private static void setDefault(String newDefault) throws Exception {
Field staticField = Person.class.getDeclaredField("DEFAULT");
setValue(null, staticField, newDefault);
}
/**
*
* Set the value of a field reflectively.
*/
protected static void setValue(Object owner, Field field, Object value) throws Exception {
makeModifiable(field);
field.set(owner, value);
}
/**
*
* Force the field to be modifiable and accessible.
*/
protected static void makeModifiable(Field nameField) throws Exception {
nameField.setAccessible(true);
int modifiers = nameField.getModifiers();
Field modifierField = nameField.getClass().getDeclaredField("modifiers");
modifiers = modifiers & ~Modifier.FINAL;
modifierField.setAccessible(true);
modifierField.setInt(nameField, modifiers);
}
}
/**
* An immutable data class.
*/
class Person {
// avoid inlining by calling newString("unnamed")
public static final String DEFAULT = new String("unnamed");
private final String name;
public Person(String name) {
this.name = name == null ? DEFAULT : name;
}
public String getName() {
return name;
}
}
view raw Doh.java hosted with ❤ by GitHub
But keep in mind: Don't do this at home!