web-dev-qa-db-fra.com

Modifier le champ final statique privé à l'aide de la réflexion Java

J'ai une classe avec un champ private static final que, malheureusement, je dois changer au moment de l'exécution.

En utilisant la réflexion, j'obtiens cette erreur: Java.lang.IllegalAccessException: Can not set static final boolean field

Est-il possible de changer la valeur?

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

En supposant que non SecurityManager vous empêche de le faire, vous pouvez utiliser setAccessible pour contourner private et réinitialiser le modificateur afin de supprimer final, et modifier en réalité un champ private static final.

Voici un exemple:

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"
   }
}

En supposant qu'aucune SecurityException ne soit levée, le code ci-dessus imprime "Everything is true".

Ce qui est réellement fait ici est le suivant:

  • Les primitives boolean, les valeurs true et false dans main, sont associées au type de référence Boolean "constantes" Boolean.TRUE et Boolean.FALSE.
  • La réflexion est utilisée pour changer le public static final Boolean.FALSE en référence à la Boolean à laquelle se réfère le Boolean.TRUE
  • Par conséquent, chaque fois qu'une false est automatiquement associée à Boolean.FALSE, elle fait référence à la même Boolean que celle à laquelle Boolean.TRUE fait référence.
  • Tout ce qui était "false" est maintenant "true"

Questions connexes


Mises en garde

Un soin extrême doit être pris chaque fois que vous faites quelque chose comme ça. Cela peut ne pas fonctionner, car une SecurityManager peut être présente, mais même si ce n'est pas le cas, selon le modèle d'utilisation, cela peut ne pas fonctionner.

JLS 17.5.3 Modification ultérieure des champs finaux

Dans certains cas, tels que la désérialisation, le système devra modifier les champs final d'un objet après la construction. Les champs final peuvent être modifiés via la réflexion et d'autres moyens dépendants de la mise en œuvre. Le seul modèle dans lequel cela a une sémantique raisonnable est celui dans lequel un objet est construit, puis les champs final de l'objet sont mis à jour. L'objet ne doit pas être rendu visible par d'autres threads, ni les champs final doivent être lus tant que toutes les mises à jour des champs final de l'objet ne sont pas terminées. Les gels d'un champ final se produisent à la fin du constructeur dans lequel le champ final est défini et immédiatement après chaque modification d'un champ final via une réflexion ou un autre mécanisme spécial.

Même dans ce cas, il existe un certain nombre de complications. Si un champ final est initialisé à une constante de compilation dans la déclaration de champ, les modifications apportées au champ final peuvent ne pas être observées, car les utilisations de ce champ final sont remplacées lors de la compilation par la constante de compilation.

Un autre problème est que la spécification permet une optimisation agressive des champs final. Dans un thread, il est permis de réorganiser les lectures d'un champ final avec les modifications d'un champ final qui ne se produisent pas dans le constructeur.

Voir également

  • JLS 15.28 Expression constante
    • Il est peu probable que cette technique fonctionne avec une primitive private static final boolean, car elle est insérable en tant que constante de compilation et la "nouvelle" valeur peut donc ne pas être observable.

Annexe: Sur la manipulation au niveau du bit

Essentiellement,

field.getModifiers() & ~Modifier.FINAL

désactive le bit correspondant à Modifier.FINAL à partir de field.getModifiers(). & est le bitwise-and, et ~ est le complément bitwise.

Voir également


Rappelez-vous les expressions constantes

Ne pas toujours être en mesure de résoudre ce problème?, Sont tombés dans la dépression comme je l'ai fait pour cela? Votre code ressemble à ceci?

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

En lisant les commentaires sur cette réponse, en particulier celui de @Pshemo, il m’a rappelé que Les expressions constantes sont traitées différemment; il sera donc impossible de le modifier. Par conséquent, vous devrez changer votre code pour ressembler à ceci:

public class A {
    private final String myVar;

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

si tu n'es pas le propriétaire de la classe ... je te sens!

Pour plus de détails sur pourquoi ce comportement lisez ceci ?

775
polygenelubricants

Si la valeur attribuée à un champ static final boolean est connue au moment de la compilation, il s'agit d'un constant. Les champs de type primitif ou String peuvent être des constantes au moment de la compilation. Une constante sera insérée dans tout code faisant référence au champ. Étant donné que le champ n'est pas lu au moment de l'exécution, sa modification n'aura aucun effet.

La spécification du langage Java dit ceci:

Si un champ est une variable constante (§4.12.4), puis en supprimant le mot clé final ou changer sa valeur ne sera pas rompre la compatibilité avec les préexistants binaires en les empêchant de s'exécuter, mais ils ne verront aucune nouvelle valeur pour l'utilisation du champ à moins qu'ils sont recompilés. Ceci est vrai même si l'utilisation en elle-même n'est pas une compilation expression constante (§15.28)

Voici un exemple:

class Flag {
  static final boolean FLAG = true;
}

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

Si vous décompilez Checker, vous verrez qu'au lieu de référencer Flag.FLAG, le code ajoute simplement une valeur de 1 (true) à la pile (instruction 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
47
erickson

Une petite curiosité tirée de la spécification du langage Java, chapitre 17, section 17.5.4 "Champs protégés en écriture":

Normalement, un champ final et statique ne peut pas être modifié . Cependant, System.in, System.out et System.err sont des champs finaux statiques que, pour des raisons historiques, il doit être autorisé à être modifié par les méthodes System.setIn, System.setOut et System.setErr. Nous nous référons à ceux-ci Les champs sont protégés en écriture pour les distinguer des fichiers ordinaires champs finaux.

Source: http://docs.Oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4

12
Stephan Markwalder

Je l'ai aussi intégrée à joor library

Juste utiliser 

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

J'ai également corrigé un problème avec override qui semblait manquer aux solutions précédentes . Cependant, utilisez-le avec précaution, uniquement lorsqu'il n'existe aucune autre solution valable.

6
iirekm

Avec la réponse la mieux classée, vous pouvez utiliser une approche un peu plus simple. Apache commons FieldUtils class a déjà une méthode particulière qui peut faire le travail. S'il vous plaît, jetez un oeil à la méthode FieldUtils.removeFinalModifier. Vous devez spécifier l'instance de champ cible et l'indicateur de forçage de l'accessibilité (si vous jouez avec des champs non publics). Plus d'infos vous pouvez trouver ici .

5
nndru

En cas de présence d'un gestionnaire de sécurité, on peut utiliser AccessController.doPrivileged 

Prenant le même exemple de la réponse acceptée ci-dessus:

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"
    }
}

Dans l'expression lambda, AccessController.doPrivileged, peut être simplifié comme suit:

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

La réponse acceptée a fonctionné pour moi jusqu'à ce qu'elle soit déployée sur JDK 1.8u91 . Ensuite, j'ai réalisé qu'elle échouait à la ligne field.set(null, newValue); lorsque j'avais lu la valeur via réflexion avant d'appeler la méthode setFinalStatic.

La lecture a probablement causé une configuration différente des éléments internes de réflexion Java (à savoir Sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl en cas d'échec au lieu de Sun.reflect.UnsafeStaticObjectFieldAccessorImpl en cas de réussite), mais je ne l'ai pas développé davantage.

Comme j'avais besoin de définir temporairement une nouvelle valeur en fonction de l'ancienne valeur, puis ultérieurement, j'ai modifié le bit de signature pour fournir une fonction de calcul en externe et également renvoyer l'ancienne valeur:

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;
    } ...

Cependant, dans le cas général, cela ne serait pas suffisant.

2

Même en dépit d'être final un champ peut être modifié en dehors de l'initialiseur statique et (au moins JVM HotSpot) exécutera le bytecode parfaitement.

Le problème est que le compilateur Java ne le permet pas, mais cela peut facilement être contourné avec objectweb.asm. Voici un fichier de classe parfaitement valide qui passe la vérification du bytecode et qui a été chargé et initialisé avec succès sous 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();

En Java, la classe se présente comme suit:

public class Cl{
    private static final int fld;

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

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

qui ne peut pas être compilé avec javac, mais peut être chargé et exécuté par JVM.

JVM HotSpot applique un traitement spécial à ces classes en ce sens qu’il empêche ces "constantes" de participer au pliage constant. Cette vérification est effectuée sur le phase de réécriture du bytecode de l'initialisation de la 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);
            }
          }
        }
      }
    }
}

La seule restriction vérifiée par JVM HotSpot est que le champ final ne soit pas modifié en dehors de la classe à laquelle le champ final est déclaré.

0
Some Name

Si votre domaine est simplement privé, vous pouvez le faire:

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

et lancer/gérer NoSuchFieldException

0
Philip Rego