Search code examples
javaclassreflectionprimitive

Changing static variable works Primitive Wrapper but not with Primitive type


I have a situation where I have to change java constant.

I have below code working

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

public class Main {
    public static final Integer FLAG = 44;

    static void setFinalStatic(Class<?> clazz, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException {
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        Field modifiers = field.getClass().getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }

    public static void main(String... args) throws Exception {
        System.out.printf("Everything is %s%n", FLAG);
        setFinalStatic(Main.class, "FLAG", 33);
        System.out.printf("Everything is %s%n", FLAG);
    }
}

If I run above , I get following output:

Everything is 44
Everything is 33

But if I change FLAG variable to int i.e.

public static final int FLAG = 44;

It does not work. The output is :

Everything is 44
Everything is 44

Is there any other way to make it work with Primitive Type int.


Solution

  • From jls-4.12.4

    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.

    Also section 13.1 says (emphasis mine)

    3..References to fields that are constant variables (§4.12.4) are resolved at compile time to the constant value that is denoted. No reference to such a field should be present in the code in a binary file (except in the class or interface containing the field, which will have code to initialize it). Such a field must always appear to have been initialized (§12.4.2); the default initial value for the type of such a field must never be observed.

    It means that compile-time constant expression from constant variables will be put directly in code by compiler (it will be inlined) not read from final reference at runtime.

    For instance if you execute main method from Bar class

    class Foo{
        static{
            System.out.println("test if class will be loaded");
        }
        public static final int x = 42;
    }
    
    class Bar{
        public static void main(String [] args){
            System.out.println(Foo.x);
        }
    }
    

    you will see no output from static block of Foo class which means Foo class hasn't been loaded, which means that value of Foo.x didn't come from this class. In fact Bar was compiled this way

    class Bar{
        public static void main(String [] args){
            System.out.println(42); // reference Foo.x will be removed by compiler 
                                    // and replaced with actual value because
                                    // compiler assumes that value can't/shouldn't
                                    // change at runtime
        }
    }
    

    So even changing value of Foo.x at runtime will not affect value printed in main method in Bar class.

    You can't change that mechanism.


    Possible way around would be initializing your final fields with values created at runtime time (they wouldn't exist at compile time, which will prevent them from being compile-time constant expressions). So instead of

    public static final String x = "foo";
    

    try

    public static final String x = new String("foo");
    

    or in case of primitive types use Unboxing like instead of

    public static final int x = 42;
    

    use

    public static final int x = new Integer(42);