5

Based on Change private static final field using Java reflection, I tried to set a private static final field.

(I know this is terribly hacky, but this question is not about code quality; it's about Java reflection.)

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

class Main {

  static class Foo {
    private static final int A = 1;

    int getA() {
      return A;
    }
  }

  public static void main(String[] args) throws Exception {
    Field modifiers = Field.class.getDeclaredField("modifiers");
    modifiers.setAccessible(true);

    Field field = Foo.class.getDeclaredField("A");
    field.setAccessible(true);
    modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    field.set(null, 2);

    System.out.println(new Foo().getA()); // should print 2
  }

}

This prints

1

I've tried this with OpenJDK 6 and 7, and Oracle 7.

I don't know what guarantees Java reflection gives. But if it failed, I thought there would be an Exception (practically all the reflection methods throw exceptions).

What is happening here?

Community
  • 1
  • 1
Paul Draper
  • 71,663
  • 43
  • 186
  • 262

1 Answers1

9

Java inlines final fields that are initialized to constant expressions at compile time.

According to the Java Language Specification, any static final* field initialized with an expression that can be evaluated at compile time must be compiled to byte code that "inlines" the field value. That is, no dynamic link will be present inside class Main telling it to obtain the value for A from InterfaceA at runtime.

Decompile the bytecode and you'll find that the body of getA() simply pushes the constant 1 and returns it.


* - The JavaWorld quote says static final. Kumar points out that the static is not required by the language specification in the definition of a constant variable. I think Kumar is right and JavaWorld is in error.

Mike Samuel
  • 114,030
  • 30
  • 209
  • 240
  • 3
    +1 OP, it didn't _fail_. Try `System.out.println(field.get(null)); // should print 2`. – Sotirios Delimanolis Aug 20 '14 at 14:47
  • @SotiriosDelimanolis, that's an excellent test to elucidate interpreter behavior. – Mike Samuel Aug 20 '14 at 14:50
  • @SotiriosDelimanolis, right, I did try that, and it worked. At the time I just thought it was lying. – Paul Draper Aug 20 '14 at 14:50
  • @MikeSamuel Ah, just to see all (2) sides of the coin. – Sotirios Delimanolis Aug 20 '14 at 14:52
  • 1
    @MikeSamuel I think it should work for any final variable initialized with an expression that can be evaluated at compile time,static is not a requirement – Kumar Abhinav Aug 20 '14 at 15:10
  • 1
    @KumarAbhinav, I think you're right: [JLS 4.12.4](http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.4) says "A variable of primitive type or type `String`, that is `final` and initialized with a compile-time constant expression (§15.28), is called a *constant variable*" without any reference to `static`. – Mike Samuel Aug 20 '14 at 15:49
  • 1
    @PaulDraper, nice. I hadn't seen those caveats re reordering of reads and writes to `final` fields that occur outside the constructor. – Mike Samuel Aug 21 '14 at 03:12