You CAN Change Final Fields

Tim Vernum pointed out a mistake in my last post. I said this:

Yikes. As expected, you CANNOT change final fields. Phew! But surprisingly, attempting to set the value does not throw an exception. Instead, it silently ignores your request.

Which is completely wrong. You can change final fields. And the results are surprising to say the least, so even though you can do this, you probably should not.

Let’s Change It

Recall the Caveman class, with its final age field:

public class Caveman {
    private String name;
    private final int age = 10;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Since the age is final and private, we must use reflection if we wish to change it:

Field ageField = Caveman.class.getDeclaredField("age");

// this lets us change private fields
ageField.setAccessible(true);

ageField.setInt(caveman, 100);

System.out.println("Age is now " + caveman.getAge());

This is where I got confused, because the program prints:

Age is now 10

My Mistake

If you run the program in a debugger and inspect the age, you will find it is actually changed to 100. The program prints 10, however, since the compiler optimized the code. Despite the fact that I used reflection to change the value, the optimized method is still…well…optimized.

As you can see, this can lead to very confusing results. For instance, you can use reflection to obtain the correct value:

System.out.println("REAL age is: " + ageField.getInt(caveman));

This gets the new 100 value, instead of the original 10.

Now Wrap Your Noodle Around This

Now change the Caveman class to initialize the age in its constructor, instead:

public class Caveman {
    private String name;
    private final int age;

    public Caveman() {
        age = 10;
    }

   ...

Now when you call the getAge() method, even after changing the field via reflection, you get the correct answer of 100.

In Summary

Here is what I learned:

  • You CAN change final fields via reflection. (yikes)
  • The compiler sometimes optimizes things, thus your programs may not see the modified data.
  • Initialization of final fields in constructors versus inline assignment can affect the optimizations.
  • You probably SHOULD NOT attempt to change final fields via reflection, because Weird Things happen.

9 Responses to “You CAN Change Final Fields”

[...] BPM: Not Even Comic-Worthy You CAN Change Final Fields » Oct [...]

Carsten Says:

Note that setAccesible can throw a SecurityException - if there is is a SecurityManager you need to have the suppressAccessChecks permission. Thus no violation of access control, although very few apps run with a SecurityManager (virtually all Tomcat installations). Simply add to you main method: System.setSecurityManager(new SecurityManager()); and the hack will cease to work. Highly recommended if you write code that is supposed to run on an Appserver.

The optimization you have seen is technically not an optimization, final values of non-objects and String are allowed to be inlined by the compiler (you’ll find this somewhere in the JLS). If you use a Number object the first version of your code with set(caveman, Integer.valueOf(100)) will return 100.

Eric Burke Says:

@Carsten I show setSecurityManager(…) and discuss suppressAccessChecks in my previous post.

cm Says:

Hi

This optimization is called inlining. The results are not surprising.
Try to decompile the class to see what actually happens.

Altough I agree you shouldn’t change final field via reflection.

Eric Burke Says:

@cm when I wrote one line of code that set a value, and the very next line of code I “retrieved” the value and saw the old data, I WAS SURPRISED.

Artur Biesiadowski Says:

Take a look at sun.misc.Unsafe class. If you have full privilages in jvm (like you need for setAccessible(true)), you can do a lot more funny things. With Unsafe class you can just modify any place in memory to any value - including change of class of the object, overwriting pointers to one object with pointers to noncompatible objects etc. Of course, crashing jvm is easiest of those options.

So, either speak about what is possibile in java in secured environment (like applets) - or don’t be surprised by anything, because with enough trickery, you can do whatever you want by direct memory manipulation. setAccessible(true) and setting final field is just a different form of direct memory manipulation, skipping normal accessibility rules (private, final, whatever).

qinxian Says:

Should we take it as a BUG of reflection function, which broken language spec? isn’t it?

Eric Burke Says:

@qinxian none of these are bugs or broken. I never said that or even implied it. But people are apparently confused by my usage of the word “surprising”. Sigh.

I am thinking along the lines of topics mentioned in the book “Java(TM) Puzzlers: Traps, Pitfalls, and Corner Cases”. An entire book of perfectly legal, valid, non-buggy code that is surprising. If it were not surprising, the book wouldn’t have much of a reason to exist. For the record, other languages (say C++ or Ruby) have 10x the number of surprising, but legal, features.

Rémi Forax Says:

For the record, you can change final field value using reflection with
1.1, 1.2, 1.5 and 1.6 VM not with 1.3 and 1.4,
this feature was disable after 1.3 and re-enable in 1.5,
following the recommandation of JSR 133 expert group (memory model).

Rémi

Leave a Reply