Modifica il campo finale statico privato utilizzando la riflessione Java


479

Ho una classe con un private static finalcampo che, sfortunatamente, devo cambiarlo in fase di esecuzione.

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

C'è un modo per modificare il valore?

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

4
Che cattiva idea. Proverei invece a ottenere il sorgente e ricompilare (o addirittura decompilare / ricompilare).
Bill K,

System.out è un campo finale statico pubblico, ma può anche essere modificato.
indiscutibile il

19
Gli @irreputable System.out/in/errsono così "speciali" che il modello di memoria Java deve menzionarli in modo particolare. Non sono esempi da seguire.
Tom Hawtin - tackline

8
beh, il mio punto è trovare un hack nel mezzo per far funzionare la mia app fino a quando il responsabile della
libreria

1
@Bill K di dieci anni fa: Sarebbe FANTASTICO ricompilarlo ma è su un sistema distribuito e devo solo correggerlo fino a quando non potremo aggiornare l'app distribuita!
Bill K,

Risposte:


888

Supponendo che no SecurityManagerti impedisca di farlo, puoi usarlo setAccessibleper aggirare privatee ripristinare il modificatore per sbarazzartene finale modificare effettivamente un private static finalcampo.

Ecco un esempio:

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

Supponendo che non SecurityExceptionvenga generato, viene stampato il codice sopra riportato "Everything is true".

Ciò che effettivamente viene fatto qui è il seguente:

  • I booleanvalori primitivitrue e falsein mainvengono autoboxati nel tipo di riferimento Boolean"costanti" Boolean.TRUEeBoolean.FALSE
  • La riflessione viene utilizzata per modificare il public static final Boolean.FALSEriferimento a Booleancui si fa riferimentoBoolean.TRUE
  • Di conseguenza, successivamente ogni volta che a false viene boxato automaticamente Boolean.FALSE, fa riferimento allo stessoBoolean quelloBoolean.TRUE
  • Tutto ciò che era "false" ora è"true"

Domande correlate


Avvertenze

Si dovrebbe prestare estrema attenzione ogni volta che si fa qualcosa di simile. Potrebbe non funzionare perché SecurityManagerpotrebbe essere presente a, ma anche se non funziona, a seconda del modello di utilizzo, potrebbe funzionare o meno.

JLS 17.5.3 Modifica successiva dei campi finali

In alcuni casi, come la deserializzazione, il sistema dovrà modificare i finalcampi di un oggetto dopo la costruzione. finali campi possono essere modificati tramite la riflessione e altri mezzi dipendenti dall'implementazione. L'unico modello in cui ciò ha una semantica ragionevole è quello in cui viene costruito un oggetto e quindi i finalcampi dell'oggetto vengono aggiornati. L'oggetto non deve essere reso visibile ad altri thread, né i finalcampi devono essere letti, fino al completamento di tutti gli aggiornamenti ai finalcampi dell'oggetto. I blocchi di un finalcampo si verificano sia alla fine del costruttore in cui finalè impostato il campo, sia immediatamente dopo ogni modifica di afinal campo tramite riflessione o altro meccanismo speciale.

Anche allora, ci sono una serie di complicazioni. Se un finalcampo viene inizializzato su una costante di tempo di compilazione nella dichiarazione del campo, le modifiche al finalcampo potrebbero non essere osservate, poiché gli usi di talefinal campo vengono sostituiti al momento della compilazione con la costante di tempo di compilazione.

Un altro problema è che le specifiche consentono un'ottimizzazione aggressiva dei finalcampi. All'interno di un thread, è consentito riordinare le letture di un finalcampo con quelle modifiche di un campo finale che non avvengono nel costruttore.

Guarda anche

  • JLS 15.28 Espressione costante
    • È improbabile che questa tecnica private static final booleanfunzioni con una primitiva , perché è inlineggiabile come costante di compilazione e quindi il valore "nuovo" potrebbe non essere osservabile

Appendice: sulla manipolazione bit a bit

Essenzialmente,

field.getModifiers() & ~Modifier.FINAL

disattiva il bit corrispondente a Modifier.FINALda field.getModifiers(). &è il bit per bit e ~il complemento per bit.

Guarda anche


Ricorda le espressioni costanti

Non riesci ancora a risolverlo? Sei caduto in depressione come ho fatto io per questo? Il tuo codice è simile al seguente?

public class A {
    private final String myVar = "Some Value";
}

Leggendo i commenti su questa risposta, in particolare quello di @Pshemo, mi ha ricordato che le espressioni costanti sono gestite in modo diverso, quindi sarà impossibile modificarlo. Quindi dovrai cambiare il tuo codice per assomigliare a questo:

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}

se non sei il proprietario della classe ... ti sento!

Per maggiori dettagli sul perché questo comportamento leggi questo ?


41
@thecoop, @HalfBrian: non c'è dubbio che questo è ESTREMAMENTE MALE , ma questo esempio è stato scelto dal design. La mia risposta mostra solo come, in alcune circostanze, ciò sia possibile. L'esempio più disgustoso che mi viene in mente è scelto deliberatamente con la speranza che forse le persone sarebbero immediatamente disgustate invece di innamorarsi della tecnica.
poligenilubrificanti,

59
Yo, amico. Ti ho sentito come un riflesso, quindi ho riflettuto sul campo in modo che tu possa riflettere mentre rifletti.
Matthew Flaschen,

11
Nota che Boolean.FALSE non è privato. Funziona davvero con i membri "private final static"?
martedì

15
@mgaert lo fa, ma devi usare getDeclaredField()invece che getField()per la classe target
eis

11
+1. Per coloro che proveranno a cambiare qualcosa di simile final String myConstant = "x";e falliranno: ricorda che le costanti di tempo di compilazione saranno inserite nel compilatore, quindi quando scriverai codice simile System.out.println(myConstant);verrà compilato come System.out.println("x");perché il compilatore conosce il valore di costante al momento della compilazione. Per sbarazzarsi di questo problema è necessario inizializzare le costanti in fase di runtime come final String myConstant = new String("x");. Anche in caso di primitivi come l' final int myField = 11uso final int myField = new Integer(11);ofinal Integer myField = 11;
Pshemo

58

Se il valore assegnato a un static final booleancampo è noto al momento della compilazione, è una costante. Campi di primitivi o String tipo possono essere costanti di compilazione-tempo. Una costante verrà incorporata in qualsiasi codice che fa riferimento al campo. Poiché il campo non viene effettivamente letto in fase di esecuzione, la modifica non avrà alcun effetto.

La specifica del linguaggio Java dice questo:

Se un campo è una variabile costante (§4.12.4), l'eliminazione della parola chiave final o la modifica del suo valore non interromperanno la compatibilità con i binari preesistenti impedendo loro di essere eseguiti, ma non vedranno alcun nuovo valore per l'uso del campo a meno che non vengano ricompilati. Ciò è vero anche se l'utilizzo 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 decompilerai Checker, vedrai che invece di fare riferimento Flag.FLAG, il codice inserisce semplicemente un valore di 1 ( true) nello stack (istruzione n. 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

Questo è stato il mio primo pensiero, ma poi mi sono ricordato che Java compilato in fase di esecuzione, se si ripristinasse il bit, si sarebbe semplicemente ricompilato con esso come variabile anziché come costante.
Bill K,

4
@Bill K - No, questo non si riferisce alla compilazione JIT. I file di classe dipendenti conterranno effettivamente i valori incorporati e nessun riferimento alla classe indipendente. È un esperimento abbastanza semplice da testare; Aggiungerò un esempio.
Erickson,

1
In che modo questo jive con la risposta di @polygenelubricants dove ridefinisce Boolean.false? - ma hai ragione, ho visto questo comportamento quando le cose non si sono ricompilate correttamente.
Bill K,

26
@Bill K - nella risposta dei poligenilubrificanti, il campo non è una costante di tempo di compilazione. Non lo public static final Boolean FALSE = new Boolean(false)èpublic static final boolean FALSE = false
erickson,

17

Un po 'di curiosità da Java Language Specification, capitolo 17, sezione 17.5.4 "Campi protetti da scrittura":

Normalmente, un campo che è finale e statico non può essere modificato. Tuttavia, System.in, System.out e System.err sono campi finali statici che, per motivi legacy, devono poter essere modificati dai metodi System.setIn, System.setOut e System.setErr. Ci riferiamo a questi campi come protetti da scrittura per distinguerli dai normali campi finali.

Fonte: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4


9

L'ho anche integrato con Joor Library

Basta usare

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

Inoltre ho risolto un problema a overridecui le precedenti soluzioni sembrano mancare. Tuttavia, usa questo con molta attenzione, solo quando non c'è altra buona soluzione.


Quando provo questo (JDK12), ottengo un'eccezione: "Impossibile impostare il campo ___ finale".
Aaron Iba,

@AaronIba Non è più consentito in Java 12+.
NateS,

7

Insieme alla risposta più votata puoi usare un approccio un po 'più semplice. La FieldUtilsclasse commons di Apache ha già un metodo particolare che può fare le cose. Per favore, dai un'occhiata al FieldUtils.removeFinalModifiermetodo. Devi specificare l'istanza del campo di destinazione e il flag di forzatura dell'accessibilità (se giochi con campi non pubblici). Maggiori informazioni puoi trovare qui .


Questa è una soluzione molto più semplice della risposta attualmente accettata
Bernie,

4
È? La copia di un metodo suona come una soluzione più semplice dell'importazione di un'intera libreria (che sta facendo la stessa cosa del metodo da copiare).
eski,

1
Non funziona in Java 12+:java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
MrPowerGamerBR

6

In caso di presenza di un Security Manager, si può fare uso di AccessController.doPrivileged

Prendendo lo stesso esempio dalla risposta accettata sopra:

import java.lang.reflect.*;

public class EverythingIsTrue {
    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");

        // wrapping setAccessible 
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                modifiersField.setAccessible(true);
                return null;
            }
        });

        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }

    public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);
      System.out.format("Everything is %s", false); // "Everything is true"
    }
}

Nell'espressione lambda AccessController.doPrivileged, può essere semplificato per:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});

1
Anche questo sembra non funzionare con Java 12+.
1

si @ dan1st, hai ragione! Si prega di verificare questo per una soluzione: stackoverflow.com/a/56043252/2546381
Vanagas

2

La risposta accettata ha funzionato per me fino a quando non è stata implementata su JDK 1.8u91. Poi mi sono reso conto che falliva in field.set(null, newValue);linea quando avevo letto il valore tramite la riflessione prima di chiamaresetFinalStatic metodo.

Probabilmente la lettura ha causato in qualche modo una diversa configurazione degli interni di riflessione Java (ovvero sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImplin caso di errore anzichésun.reflect.UnsafeStaticObjectFieldAccessorImpl in caso di successo) ma non l'ho elaborato ulteriormente.

Dato che dovevo impostare temporaneamente un nuovo valore basato sul vecchio valore e successivamente ripristinare il vecchio valore, ho cambiato un po 'la firma per fornire esternamente la funzione di calcolo e restituire anche 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 questo non sarebbe sufficiente.


2

Nonostante sia finalun campo può essere modificato al di fuori dell'inizializzatore statico e (almeno JVM HotSpot) eseguirà il bytecode perfettamente bene.

Il problema è che il compilatore Java non lo consente, ma questo può essere facilmente aggirato usando objectweb.asm. Ecco un file di classe perfettamente valido che supera la verifica del codice e viene caricato e inizializzato correttamente in JVM HotSpot OpenJDK12:

ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
    FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
    fv.visitEnd();
}
{
    // public void setFinalField1() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_5);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
{
    // public void setFinalField2() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_2);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
cw.visitEnd();

In Java, la classe appare approssimativamente come segue:

public class Cl{
    private static final int fld;

    public static void setFinalField1(){
        fld = 5;
    }

    public static void setFinalField2(){
        fld = 2;
    }
}

che non può essere compilato javac , ma può essere caricato ed eseguito da JVM.

JVM HotSpot ha un trattamento speciale di tali classi, nel senso che impedisce a tali "costanti" di partecipare al ripiegamento costante. Questo controllo viene eseguito sulla fase di riscrittura bytecode dell'inizializzazione della classe :

// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
   Symbol* field_name = cp->name_ref_at(bc_index);
   Symbol* field_sig = cp->signature_ref_at(bc_index);

   fieldDescriptor fd;
   if (klass->find_field(field_name, field_sig, &fd) != NULL) {
      if (fd.access_flags().is_final()) {
         if (fd.access_flags().is_static()) {
            if (!method->is_static_initializer()) {
               fd.set_has_initialized_final_update(true);
            }
          } else {
            if (!method->is_object_initializer()) {
              fd.set_has_initialized_final_update(true);
            }
          }
        }
      }
    }
}

L'unica limitazione verificata da JVM HotSpot è che il finalcampo non deve essere modificato al di fuori della classe in cui finalè dichiarato il campo.


0

Ho appena visto quella domanda su una delle domande dell'intervista, se possibile per cambiare la variabile finale con la riflessione o in fase di esecuzione. Mi sono davvero interessato, così che cosa sono diventato con:

 /**
 * @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 semplice classe con la 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


Hai visto questo post?
Ravindra HV,

Questa domanda fa domande su un staticcampo finale, quindi questo codice non funziona. setAccessible(true)funziona solo per l'impostazione dei campi dell'istanza finale.
Radiodef,

0

Se il tuo campo è semplicemente privato puoi farlo:

MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");

e lanciare / gestire NoSuchFieldException


-4

L'intero punto di a final campo è che non può essere riassegnato una volta impostato. La JVM utilizza questa garanzia per mantenere la coerenza in vari luoghi (ad es. Classi interne che fanno riferimento a variabili esterne). Quindi no. Essere in grado di farlo spezzerebbe la JVM!

La soluzione non è dichiararla finalin primo luogo.


4
Inoltre, finalha un ruolo speciale nell'esecuzione multithread: la modifica dei finalvalori romperebbe anche il modello di memoria Java.
Péter Török,

1
E un campo non dichiarato finalnon dovrebbe essere dichiarato static.
Tom Hawtin - tackline

2
@ Tom: In generale è probabilmente vero, ma non metterei fuori legge tutte le variabili mutabili statiche.
Bcat,

7
@ Tom: hai mai letto perché i singoli sono cattivi? L'ho fatto! Ora so che sono cattivi solo in Java. E solo a causa della disponibilità di un caricatore di classi definito dall'utente. E da quando conosco tutto questo e non uso un caricatore di classi definito dall'utente, uso singletons senza rimpianti. E così anche Scala, in cui singleton è una caratteristica linguistica di prima classe - Il fatto che i singoli siano cattivi è un falso mito ben noto .
Martin,

3
@Martin So che il tuo commento è vecchio, e forse le tue opinioni sono cambiate ormai, ma ho pensato di aggiungere solo questo: i single sono malvagi per ragioni che non hanno nulla a che fare con Java. Aggiungono complessità nascosta al tuo codice. Inoltre, possono rendere impossibile il test unitario senza sapere che anche n singleton devono essere configurati per primi. Sono l'antitesi dell'iniezione di dipendenza. La tua squadra potrebbe decidere che le insidie ​​della complessità nascosta non superano la convenienza dei Singleton, ma molte squadre adottano la posizione opposta per una buona ragione.
cotta
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.