54

Let's start with a simple test case:

import java.lang.reflect.Field;

public class Test {
  private final int primitiveInt = 42;
  private final Integer wrappedInt = 42;
  private final String stringValue = "42";

  public int getPrimitiveInt()   { return this.primitiveInt; }
  public int getWrappedInt()     { return this.wrappedInt; }
  public String getStringValue() { return this.stringValue; }

  public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException {
    Field field = Test.class.getDeclaredField(name);
    field.setAccessible(true);
    field.set(this, value);
    System.out.println("reflection: " + name + " = " + field.get(this));
  }

  public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
    Test test = new Test();

    test.changeField("primitiveInt", 84);
    System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());

    test.changeField("wrappedInt", 84);
    System.out.println("direct: wrappedInt = " + test.getWrappedInt());

    test.changeField("stringValue", "84");
    System.out.println("direct: stringValue = " + test.getStringValue());
  }
}

Anybody care to guess what will be printed as output (shown at the bottom as to not spoil the surprise immediately).

The questions are:

  1. Why do primitive and wrapped integer behave differently?
  2. Why does reflective vs direct access return different results?
  3. The one that plagues me most - why does String behave like primitive int and not like Integer?

Results (java 1.5):

reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
ChssPly76
  • 99,456
  • 24
  • 206
  • 195

5 Answers5

22

Compile-time constants are inlined (at javac compile-time). See the JLS, in particular 15.28 defines a constant expression and 13.4.9 discusses binary compatibility or final fields and constants.

If you make the field non-final or assign a non-compile time constant, the value is not inlined. For instance:

private final String stringValue = null!=null?"": "42";

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • So they are. For primitive int I expected the result I got back. It's String that puzzles me. Do you have a more concrete link to JLS where that is described? – ChssPly76 Oct 23 '09 at 18:43
  • This is certainly part of the answer, but how is it that if you acces the string via the reflection field.get you get a different value than if you ask the object directly? – Steve B. Oct 23 '09 at 18:45
  • ChssPly76: Added the two interesting sections. Steve B: If you use reflection you go back to the value that the reflected class file has set to that field. If you reference it directly, the value is copied at (javac) compile-time (so long as it is a compile-time constant). – Tom Hawtin - tackline Oct 23 '09 at 18:47
  • @Tom - thank you. I find it strange that JLS gives special treatment to String but not primitive wrappers in this matter, but it is what it is. Constant inlining description could have been a lot better; it you haven't pointed the exact paragraph I would have never found it. – ChssPly76 Oct 23 '09 at 18:58
  • This almost makes sense... except see my answer (forthcoming). – Jason S Oct 23 '09 at 22:06
  • The special behavior for the string comes from String interning. Strings defined at compile time are stored in a special pool in the JVM (which you can add strings to with String.intern()). The compiler will then treat the reference to this interned String as a constant. The compiler has directly replaced all references to stringValue with the reference to the single instance of "42" that exists in the interned strings pool. – LordOfThePigs Oct 19 '12 at 07:58
10

In my opinion this is even worse: A colleague pointed to the following funny thing:

@Test public void  testInteger() throws SecurityException,  NoSuchFieldException, IllegalArgumentException, IllegalAccessException  {      
    Field value = Integer.class.getDeclaredField("value");      
    value.setAccessible(true);       
    Integer manipulatedInt = Integer.valueOf(7);      
    value.setInt(manipulatedInt, 666);       
    Integer testInt = Integer.valueOf(7);      
    System.out.println(testInt.toString());
}

By doing this, you can change the behaviour of the whole JVM you are running in. (of course you can change only the values for the values between -127 and 127)

GandalfIX
  • 261
  • 1
  • 3
  • 8
  • Actually I think that Integers might have a value for the upper limit of the cache, so you could go higher than that! – Octavia Togami May 14 '14 at 17:54
  • 1
    It reads even funnier without the `valueOf` calls, i.e. `value.setInt(7, 666); Integer testInt = 7; System.out.println(testInt);` However, with recent Java versions, such manipulations are not that easy anymore. – Holger Sep 26 '22 at 14:44
8

Reflection's set(..) method works with FieldAccessors.

For int it gets an UnsafeQualifiedIntegerFieldAccessorImpl, whose superclass defines the readOnly property to be true only if the field is both static and final

So to first answer the unasked question - here's why the final is changed without exception.

All subclasses of UnsafeQualifiedFieldAccessor use the sun.misc.Unsafe class to get the values. The methods there are all native, but their names are getVolatileInt(..) and getInt(..) (getVolatileObject(..) and getObject(..) respectively). The aforementioned accessors use the "volatile" version. Here's what happens if we add the non-volatile version:

System.out.println("reflection: non-volatile primitiveInt = "
     unsafe.getInt(test, (long) unsafe.fieldOffset(getField("primitiveInt"))));

(where unsafe is instantiated by reflection - it is not allowed otherwise) (and I call getObject for Integer and String)

That gives some interesting results:

reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: non-volatile primitiveInt = 84
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: non-volatile wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
reflection: non-volatile stringValue = 84

At this point I recall an article at javaspecialists.eu discussing an related matter. It quotes JSR-133:

If a final field is initialized to a compile-time constant in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the compile-time constant.

Chapter 9 discusses the details observed in this question.

And it turns out this behaviour is not that unexpected, since modifications of final fields are supposed to happen only right after initialization of the object.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
1

This is not an answer, but it brings up another point of confusion:

I wanted to see if the issue was compile-time evaluation or whether the reflection was actually allowing Java to get around the final keyword. Here's a test program. All I added was another set of getter calls, so there's one before and after each changeField() call.

package com.example.gotchas;

import java.lang.reflect.Field;

public class MostlyFinal {
  private final int primitiveInt = 42;
  private final Integer wrappedInt = 42;
  private final String stringValue = "42";

  public int getPrimitiveInt()   { return this.primitiveInt; }
  public int getWrappedInt()     { return this.wrappedInt; }
  public String getStringValue() { return this.stringValue; }

  public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException {
    Field field = MostlyFinal.class.getDeclaredField(name);
    field.setAccessible(true);
    field.set(this, value);
    System.out.println("reflection: " + name + " = " + field.get(this));
  }

  public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
    MostlyFinal test = new MostlyFinal();

    System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
    test.changeField("primitiveInt", 84);
    System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());

    System.out.println();

    System.out.println("direct: wrappedInt = " + test.getWrappedInt());
    test.changeField("wrappedInt", 84);
    System.out.println("direct: wrappedInt = " + test.getWrappedInt());

    System.out.println();

    System.out.println("direct: stringValue = " + test.getStringValue());
    test.changeField("stringValue", "84");
    System.out.println("direct: stringValue = " + test.getStringValue());
  }
}

Here's the output I get (under Eclipse, Java 1.6)

direct: primitiveInt = 42
reflection: primitiveInt = 84
direct: primitiveInt = 42

direct: wrappedInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84

direct: stringValue = 42
reflection: stringValue = 84
direct: stringValue = 42

Why the heck does the direct call to getWrappedInt() change ?

Jason S
  • 184,598
  • 164
  • 608
  • 970
  • 6
    The real question is "why the other two do not change?" And the answer is - because their values are inlined in getters (Tom's answer points to JLS sections describing this behavior). Inlining only happens for primitive types and String (assuming the other conditions like final / constant expression / etc... are satisfied). Thus int and String return 42 from getters; Integer does not. The **actual** values have been changed for all 3 fields. If this is confusing - and it is :-) - decompile the class and you'll see what I mean. – ChssPly76 Oct 23 '09 at 22:19
0

There is a work around for this. if you set the value of the private static final filed in the static {} block it will work because it will not inline the fileld:

private static final String MY_FIELD;

static {
    MY_FIELD = "SomeText"
}

...

Field field = VisitorId.class.getDeclaredField("MY_FIELD");

field.setAccessible(true);
field.set(field, "fakeText");
Joan P.S
  • 1,553
  • 1
  • 16
  • 42