8

I recently stumbled upon Change private static final field using Java reflection and tested polygenelubricants' EverythingIsTrue class, works fine, System.out.format("Everything is %s", false); prints Everything is true indeed. But when I change the code as

public class EverythingIsTrue {

    public static final boolean FALSE = false;

    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }

    public static void main(String[] args) throws Exception {
        setFinalStatic(EverythingIsTrue.class.getField("FALSE"), true);
        System.out.format("Everything is %s", FALSE);
    }
}

it prints

Everything is false

Does anybody know why? Does setFinalStatic actually work or not?

MC Emperor
  • 22,334
  • 15
  • 80
  • 130
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275

2 Answers2

24

You can avoid compiler inlining by making the value a result of a method call, even a dummy one.

public class Main {
    // value is not known at compile time, so not inlined
    public static final boolean FLAG = Boolean.parseBoolean("false");

    static void setFinalStatic(Class clazz, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException {
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        Field modifiers = field.getClass().getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }

    public static void main(String... args) throws Exception {
        System.out.printf("Everything is %s%n", FLAG);
        setFinalStatic(Main.class, "FLAG", true);
        System.out.printf("Everything is %s%n", FLAG);
    }
}

prints

Everything is false
Everything is true
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • This is awesome! Using a generic method you can make a dummy method for non primitives `static T placeHolder() { return null; }` – flakes Feb 09 '17 at 07:16
  • @dit It is console output, so it should not be formatted as if it were source code. – MC Emperor Dec 20 '17 at 11:10
  • @mc-emperior I think the idea was to highlight the `true` and `false` to make the difference more clear. – alex Dec 20 '17 at 16:25
17

When accessing primitive static final fields, the Java compiler will assume that the value is a constant and inline the value instead of generating code that accesses the field. This means that the compiler will replace with the reference to the FALSE field with the value false. If you use reflection to access the field, you will see that the value of the field has actually changed.

This will not work for non-primitive fields, as the value of an object reference can not be inlined at compile time.

MC Emperor
  • 22,334
  • 15
  • 80
  • 130
Jan B
  • 399
  • 3
  • 8
  • 1
    Which is exactly the same answer as mine - only with more words! – dty Dec 31 '12 at 13:20
  • @dty it looks like it are infinite more words even ;) – Mike Braun Dec 31 '12 at 14:29
  • I deleted my answer as yours had been accepted and mine wasn't adding any value - only brevity! ;-) – dty Dec 31 '12 at 17:02
  • 3
    Actually, it doesn't *assume* that it is constant. The compiler *analyses* the expression to see if it is a "constant expression" as defined by the JLS. If it is, then it evaluates it and inlines the value. For instance, `public static final boolean TRUE = "".isEmpty();` will NOT be inlined, even though it is obvious to you and me that the value is always `true`. The reason is that that expression is not a "constant expression" according to the JLS. – Stephen C Sep 27 '13 at 07:53