2

Suppose I have the following class:

public class SomeClass {

    private final int num;

    public SomeClass(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }

}

When I execute this code to set the num field, everything is fine:

SomeClass obj = new SomeClass(0);

final Field field = SomeClass.class.getDeclaredField("num");
field.setAccessible(true);
Field modField = Field.class.getDeclaredField("modifiers");
modField.setAccessible(true);
modField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(obj, 1);

System.out.println(obj.getNum()); // Prints 1 instead of the initial value 0.

However, when I remove the constructor from SomeClass, this doesn't work anymore and the println statement prints 0.

Can anyone explain this behavior?

beatngu13
  • 7,201
  • 6
  • 37
  • 66
  • Note that you'd get the same behavior even if the field is `public`. The behavior is like this because the field is `final`. Access modifiers do not matter in this case. – Sergey Kalinichenko Dec 26 '17 at 20:00
  • It might be that the compiler replaced the reference to the num field with a constant 0 value since this field is final. You should consider using javap to determine if that is the case. – Claudio Corsi Dec 26 '17 at 20:09

2 Answers2

2

First, note that you'd get the same behavior even if the field is public, in which case you wouldn't need to set the field accessible:

final Field field = SomeClass.class.getDeclaredField("num");
Field modField = Field.class.getDeclaredField("modifiers");
modField.setAccessible(true);
modField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(obj, 1);
System.out.println(obj.num);

This is the result of an optimization by Java compiler: the actual field num is set to 1, but getNum ignores the value, because the compiler thinks that it's final.

This line prints 1 even with the constructor removed (demo):

System.out.println(field.get(obj));

Java compiler notices that final int num is never assigned outside its initializer, and replaces return num with returning num's initial value.

Note: Your experiment offers an excellent reason to why one should not attempt modifying fields that you declare non-modifiable.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • It still print `0` even if you make field public and access it like `obj.num`. – tsolakp Dec 26 '17 at 19:55
  • @tsolakp This shows that the compiler applies the same optimization for accesses to final fields from outside the class as for accesses to final fields from inside the class. – Sergey Kalinichenko Dec 26 '17 at 19:58
2

Lets look at the Java doc for Field.set method:

If the underlying field is final, the method throws an IllegalAccessException unless setAccessible(true) has succeeded for this Field object and the field is non-static. Setting a final field in this way is meaningful only during deserialization or reconstruction of instances of classes with blank final fields, before they are made available for access by other parts of a program. Use in any other context may have unpredictable effects, including cases in which other parts of a program continue to use the original value of this field.

What this means is that in your example if you remove constructor you need to initialize the final field to some value and thus make it non blank. In that case if you change final field using reflection you can have unpredictable effects, including cases in which other parts of a program continue to use the original value of this field.

tsolakp
  • 5,858
  • 1
  • 22
  • 28
  • "What this means is that in your example if you remove constructor you need to initialize the final field to some value and thus make it non blank" Of course he does, otherwise the code wouldn't even compile. – Sergey Kalinichenko Dec 26 '17 at 20:02
  • @dasblinkenlight. Exactly. And according to Java doc he is seeing the described behavior. – tsolakp Dec 26 '17 at 20:03