stonar96
stonar96

Reputation: 1461

Java reflection does not work as expected

I just wrote this code to test something for a better understanding of reflection.

This is the ReflectionTestMain class:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionTestMain {
    public static void main(String[] args) {
        try {
            ReflectionTest rt = new ReflectionTest();
            Class<ReflectionTest> c = ReflectionTest.class;
            Field f = c.getDeclaredField("value");
            f.setAccessible(true);
            f.set(rt, "text");
            Method m = c.getDeclaredMethod("getValue");
            m.setAccessible(true);
            String value = (String) m.invoke(rt);
            System.out.println(value);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

And this is the ReflectionTest class.

public class ReflectionTest {
    private final String value = "test";

    private String getValue() {
        return value;
    }
}

This code prints test but I expected it prints text. What is the reason that this does not work and how can I fix that?

Upvotes: 3

Views: 1016

Answers (2)

Ferrybig
Ferrybig

Reputation: 18824

While the variable is properly updated, it isn't propagated to the getValue() method.

The reason for this is that the compiler optimizes the program for you.

Since the compiler knows that the variable value is not changed, it compiles it to inline access directly to the string pool, instead of going through the variable. This can be sees by running java -p on the class file

This can be solved by using a initizer block or constructor for the string constant, or making the statement more complex to "fool" the compiler.

class ReflectionTest {


    // Use either
    private final String value;
    {
        value = "test";
    }
    // Or 
    private final String value;
    public ReflectionTest () {
        value = "test";
    }
    // Or
    private final String value = Function.identity().apply("test");
    // Or

    //   Do not replace with + as the compiler is too smart
    private final String value = "test".concat(""); 
    // Depending on your required performance/codestyling analyses

    private String getValue() {
        return value;
    }

}

Upvotes: 5

ekaerovets
ekaerovets

Reputation: 1168

From javadoc on Field.set(..)

 * <p>If the underlying field is final, the method throws an
 * {@code IllegalAccessException} unless {@code setAccessible(true)}
 * has succeeded for this {@code Field} object
 * and the field is non-static. Setting a final field in this way
 * is meaningful only during deserialization or reconstruction of
 * instances of classes with blank final fields, before they are
 * made available for access by other parts of a program. Use in
 * any other context may have unpredictable effects, including cases
 * in which other parts of a program continue to use the original
 * value of this field.

So you just use reflection in an incorrect way here.

Upvotes: 1

Related Questions