Pergunta

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.

Foi útil?

Solução

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);

Outras dicas

  1. Primitive types get inlined.

  2. In fact even non-primitive constants, when imported into other classes, will be copied, and the import forgotten. So there it will not work either. Only for constant caches, like the string pool, and Integer (Integer.valueOf(13)) caches you may overwrite their values.

This happens because static final fields of primitive or String type get inlined during compile time. The main method will looks something like this after compilation and de-compilation

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

because FLAG gets replaced with actual value in compile-time.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top