Cambia il campo finale statico privato usando la riflessione Java


Answers

Se il valore assegnato a un campo static final boolean è noto in fase di compilazione, è una costante. I campi di tipo primitivo o String possono essere costanti in fase di compilazione. Una costante sarà indicata in qualsiasi codice che faccia riferimento al campo. Poiché il campo non viene effettivamente letto in fase di runtime, la sua modifica non avrà alcun effetto.

Le specifiche del linguaggio Java dicono questo:

Se un campo è una variabile costante (§4.12.4), l'eliminazione della parola chiave final o la modifica del suo valore non interromperà la compatibilità con i file binari preesistenti, facendo sì che non vengano eseguiti, ma non vedranno alcun nuovo valore per l'utilizzo del campo a meno che non siano ricompilati. Questo è vero anche se l'uso stesso non è un'espressione costante in fase di compilazione (§15.28)

Ecco un esempio:

class Flag {
  static final boolean FLAG = true;
}

class Checker {
  public static void main(String... argv) {
    System.out.println(Flag.FLAG);
  }
}

Se decompilate il Checker , vedrete che invece di fare riferimento a Flag.FLAG , il codice semplicemente spinge un valore di 1 ( true ) nello stack (istruzione # 3).

0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3:   iconst_1
4:   invokevirtual   #3; //Method java/io/PrintStream.println:(Z)V
7:   return
Question

Ho una classe con un campo private static final che, sfortunatamente, ho bisogno di cambiare in fase di esecuzione.

Usando la riflessione ottengo questo errore: java.lang.IllegalAccessException: Can not set static final boolean field

C'è un modo per cambiare il valore?

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);



Ho appena visto quella domanda su una delle domande dell'intervista, se possibile per modificare la variabile finale con la riflessione o in runtime. Mi sono davvero interessato, quindi quello con cui sono diventato:

 /**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class SomeClass {

    private final String str;

    SomeClass(){
        this.str = "This is the string that never changes!";
    }

    public String getStr() {
        return str;
    }

    @Override
    public String toString() {
        return "Class name: " + getClass() + " Value: " + getStr();
    }
}

Qualche classe semplice con variabile String finale. Quindi nella classe principale import java.lang.reflect.Field;

/**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class Main {


    public static void main(String[] args) throws Exception{

        SomeClass someClass = new SomeClass();
        System.out.println(someClass);

        Field field = someClass.getClass().getDeclaredField("str");
        field.setAccessible(true);

        field.set(someClass, "There you are");

        System.out.println(someClass);
    }
}

L'output sarà il seguente:

Class name: class SomeClass Value: This is the string that never changes!
Class name: class SomeClass Value: There you are

Process finished with exit code 0

Secondo la documentazione https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html




L'ho anche integrato con la libreria joor

Basta usare

      Reflect.on(yourObject).setFinal("finalFieldName", finalFieldValue);

Inoltre ho risolto un problema con override che le soluzioni precedenti sembrano mancare. Comunque usa questo molto attentamente, solo quando non c'è altra buona soluzione.




La risposta accettata ha funzionato per me fino a quando non è stata implementata su JDK 1.8u91. Poi ho capito che falliva su field.set(null, newValue); linea quando avevo letto il valore tramite riflessione prima di chiamare il metodo setFinalStatic .

Probabilmente la lettura ha causato l'impostazione in qualche modo diversa degli interni di riflessione Java (vale a dire sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl in caso di sun.reflect.UnsafeStaticObjectFieldAccessorImpl invece di sun.reflect.UnsafeStaticObjectFieldAccessorImpl in caso di successo) ma non l'ho ulteriormente elaborato.

Poiché avevo bisogno di impostare temporaneamente un nuovo valore in base al vecchio valore e successivamente impostare il valore precedente, ho modificato la firma per fornire la funzione di calcolo esternamente e anche restituire il vecchio valore:

public static <T> T assignFinalField(Object object, Class<?> clazz, String fieldName, UnaryOperator<T> newValueFunction) {
    Field f = null, ff = null;
    try {
        f = clazz.getDeclaredField(fieldName);
        final int oldM = f.getModifiers();
        final int newM = oldM & ~Modifier.FINAL;
        ff = Field.class.getDeclaredField("modifiers");
        ff.setAccessible(true);
        ff.setInt(f,newM);
        f.setAccessible(true);

        T result = (T)f.get(object);
        T newValue = newValueFunction.apply(result);

        f.set(object,newValue);
        ff.setInt(f,oldM);

        return result;
    } ...

Tuttavia, nel caso generale, ciò non sarebbe sufficiente.






Links