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
  • 78,542
  • 46
  • 206
  • 285

2 Answers2

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
  • 118,113
  • 30
  • 216
  • 245
  • 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
0

There are cases where java does not inline static final constants. And the reflection behavior is different between Java versions (such as getting modifiers field will fail in modern Java), however there's something that stayed consistent between Java versions - sun.misc.Unsafe.

You can use it to modify value of any field and bypass all of access modifiers.

    public static void setStaticObjectUnsafe(final Field field, Object value) {
        final Object staticFieldBase = theUnsafe.staticFieldBase(field);
        final long staticFieldOffset = theUnsafe.staticFieldOffset(field);
        theUnsafe.putObject(staticFieldBase, staticFieldOffset, value);
    }

For primitives it's the same except you need to replace value parameter and putObject with it's alternative for primitive type:

    public static void setStaticIntegerUnsafe(final Field field, int value) {
        final Object staticFieldBase = theUnsafe.staticFieldBase(field);
        final long staticFieldOffset = theUnsafe.staticFieldOffset(field);
        theUnsafe.putInt(staticFieldBase, staticFieldOffset, value);
    }

To get unsafe instance see this solution.

If you are using Eclipse IDE you will need to tweak compiler settings in preferences: Java -> Compiler -> Errors/Warnings -> Set "Forbidden reference (access rules)" to Ignore

Lassebq
  • 1
  • 1
  • And again an other solution that [does not work](https://www.ideone.com/3Xv5Jo). – Johannes Kuhn Mar 14 '23 at 21:03
  • Using code you provided does in fact print 100 on my side. Tested with Java 5, 6, 8 and 17 – Lassebq Mar 16 '23 at 20:40
  • Did you read the output on ideone? It is 10. Not 100. 10. So, there is at least one platform where it does not work. (PS.: It also doesn't work on my machine.) – Johannes Kuhn Mar 16 '23 at 21:55
  • What java distro did you use and what version? – Lassebq Mar 19 '23 at 18:55
  • The code you provided failed on all java versions I tested it with when using the code I linked. Did you run it in debug mode? Did you make any changes to my code? – Johannes Kuhn Mar 20 '23 at 01:27
  • I did not edit anything. Except for underscores in the 1_000_000 because java 5 and 6 compiler did not support it. Now if you don't want to tell me which java distribution you're using, try [Oracle Java 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html), that's what I used for testing with Java 17 – Lassebq Mar 22 '23 at 15:36
  • I use the binaries from [jdk.java.net](https://jdk.java.net/), but that does not matter. Your answer simply does not work everywhere. – Johannes Kuhn Mar 22 '23 at 16:17
  • Yes it does matter. Stop being ignorant. [this is my last proof that it works](https://cdn.discordapp.com/attachments/980357983206793227/1088196064768692324/image.png) I do not care if you find it useful or not. – Lassebq Mar 22 '23 at 20:24
  • Same result with `javac` and `java`, outside of IDE. [link](https://cdn.discordapp.com/attachments/980357983206793227/1088197146500673709/image.png) – Lassebq Mar 22 '23 at 20:28
  • Strange... Increase the number of `useField()` before the change calls. 5-10 should be enough. – Johannes Kuhn Mar 22 '23 at 21:13
  • [Other example](https://www.ideone.com/0KssJX). With a ~600 iterations the output flips between 10 and 100 on my machine. – Johannes Kuhn Mar 22 '23 at 21:19
  • Well, all I can say is that the getter is the problem. If accessed directly using a field reference it gets correct value. In conclusion this solution works as long as you don't use it in extreme environments like these. Nothing should access a constant through a getter anyway. If something does, it's a programming problem. – Lassebq Mar 22 '23 at 22:20
  • Do you know what the JIT does consider as constant value? "If accessed directly" Guess what the getter does? You don't even need a getter at all. Anytime the JIT compiles a method it will inline the constants. You don't know when that will happen (there are JVMs out there that can serialize profiling data or even compiled machine code for faster startup). It also may not happen at all or just enough time later (because the compiler thread was a bit slower) so the JIT uses the new value. But you are still ignorant that it simply can't reliably work. – Johannes Kuhn Mar 23 '23 at 00:13