2

Can a package final variable be changed through reflection?

Say I have this:

public class Widget {
  final int val = 23;
}

Can the val be changed through reflection if made accessible?

If so, is there any way to prevent it that without using the security manager?

The Coordinator
  • 13,007
  • 11
  • 44
  • 73

3 Answers3

4

YES. Try this code:

public static void main(String[] args) throws Exception {
    Widget w = new Widget ();

    Field m = Widget.class.getDeclaredField("val");

    m.setAccessible(true);

    m.set(w, 233);

    System.out.println(m.get(w)); /// PRINT 233
}
LHA
  • 9,398
  • 8
  • 46
  • 85
  • But `System.out.println(w.val);` shows `23`, not `233`. So unless you're doing the access through reflection... – T.J. Crowder May 27 '14 at 16:32
  • @T.J.Crowder cool. OK, so how does that work? Why the field but not using the getter? – The Coordinator May 27 '14 at 16:43
  • @T.J.Crowder: You are right! Developers need to be aware of this! – LHA May 27 '14 at 16:44
  • @SaintHill: My guess would be that because the member is `final`, the compiler or JIT was able to inline the value as it's effectively a constant (barring people mucking about with it via reflection :-) ). I don't know the details. – T.J. Crowder May 27 '14 at 16:45
  • It is. If you make the final field something that doesn't become a compile time constant (something non-string non-primitive), it will show up in the actual object. – ILMTitan May 27 '14 at 17:49
2

Turns out, changing final members causes reflection-obtained values to differ from values returned by regular code! This is quite scary.

import java.lang.reflect.Field;

public class Test {

    private static final class Widget {
        private final int val = 23;

        public int getVal() {
            return val;
        }
    }

    public static void main(String[] args) throws Exception {
        Widget w = new Widget ();

        Field m = Widget.class.getDeclaredField("val");

        m.setAccessible(true);

        m.set(w, 233);

        Field m1 = Widget.class.getDeclaredField("val");
        m1.setAccessible(true);


        System.out.println(m.get(w)); /// PRINT 233
        System.out.println(w.getVal()); /// PRINT 23
        System.out.println(m1.get(w)); /// PRINT 233

    }
}
  • This is very weird! We need to investigate more – Ahmed Hamdy May 27 '14 at 16:49
  • 1
    `final int val = 23;` defines compile-time constant. Every access to it gets replaced by the constant value at compile-time already. Compile-time constants don’t need to be `static`, though it is useless to reserve space in every object instance of `Widget` for a field that is never read at runtime (besides when using Reflection). – Holger Jun 11 '20 at 13:08
-1

Try this.

 Widget() {
     checkPerMission();
  }
     private void checkPerMission() {
         Class self = sun.reflect.Reflection.getCallerClass(1);
          Class caller = sun.reflect.Reflection.getCallerClass(3);
         if (self != caller) {
             throw new java.lang.IllegalAccessError();
         }

 }
geddamsatish
  • 174
  • 10