Modifier le champ final statique privé en utilisant Java reflection

j'ai une classe avec un champ private static final que, malheureusement, je dois changer à l'exécution.

en utilisant la réflexion je reçois cette erreur: java.lang.IllegalAccessException: Can not set static final boolean field

y a-t-il un moyen de changer la valeur?

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
391
demandé sur smholloway 2010-07-21 20:35:48

9 réponses

en supposant qu'aucun SecurityManager ne vous empêche de le faire, vous pouvez utiliser setAccessible pour contourner private et réinitialiser le modificateur pour vous débarrasser de final , et en fait modifier 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'aucun SecurityException n'est lancé, le code ci-dessus imprime "Everything is true" .

ce qui est réellement fait ici est comme suit:

  • primitives boolean valeurs true et false dans main sont autoboxed de référence de type Boolean "" constantes Boolean.TRUE et Boolean.FALSE
  • la réflexion est utilisée pour remplacer public static final Boolean.FALSE par Boolean visé par Boolean.TRUE
  • en conséquence, par la suite chaque fois qu'un false est autoboxé à Boolean.FALSE , il se réfère à la même Boolean comme celle visée par Boolean.TRUE
  • Tout ce qui était "false" est maintenant "true" 1519590920"

questions connexes


mises en garde

il faut être extrêmement prudent chaque fois que vous faites quelque chose comme ça. Il peut ne pas fonctionner parce qu'un SecurityManager peut être présent, mais même s'il ne fonctionne pas, selon le modèle d'utilisation, il peut ou peut ne pas fonctionner.

JLS 17.5.3 Modification ultérieure des Champs finaux

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

Même alors, il ya un certain nombre de complications. Si un final champ est initialisé à une constante de compilation dans le domaine déclaration, les changements dans le champ final ne peuvent pas être observés, puisque les utilisations de ce champ final sont remplacées au moment de la compilation par la constante de temps 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éordonner les lectures d'un champ final avec les modifications d'un champ final qui n'ont pas lieu dans le constructeur.

Voir aussi

  • JLS 15.28 Constant Expression
    • il est peu probable que cette technique fonctionne avec une primitive private static final boolean , car elle est inlineable comme constante de temps de compilation et donc la" nouvelle "valeur peut ne pas être observable

Annexe: Sur le bit-à-bit de manipulation

Essentiellement,

field.getModifiers() & ~Modifier.FINAL

éteint le bit correspondant à Modifier.FINAL de field.getModifiers() . & est le bitwise-and, et ~ est le bitwise-complement.

Voir aussi


Se Souvenir Des Expressions Constantes

toujours incapable pour résoudre ce problème?, tombés sur la dépression comme je l'ai fait pour elle? Est-ce que votre code ressemble à ceci?

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

en lisant les commentaires sur cette réponse, spécialement celui de @Pshemo, il m'a rappelé que les Expressions constantes sont traitées différemment de sorte qu'il sera impossible de la modifier. Par conséquent, vous aurez besoin de changer votre code ressemble à ceci:

public class A {
    private final String myVar;

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

si vous êtes pas le titulaire de la classe... Je sens que vous!

pour plus de détails sur les raisons de ce comportement lire ce ?

742
répondu polygenelubricants 2018-07-06 06:27:57

si la valeur attribuée à un champ static final boolean est connue au moment de la compilation, il s'agit d'une constante . champs de primitif ou Le type String peut être des constantes de temps de compilation. Une constante sera insérée dans n'importe quel code qui renvoie au champ. Puisque le champ n'est pas réellement lu à l'exécution, le changer n'aura alors aucun effet.

Le Java language specification dit ceci:

si un champ est une variable constante (§4.12.4), puis supprimer le mot-clé finale ou en changeant sa valeur ne sera pas compatibilité de rupture avec les systèmes préexistants binaires en les empêchant de courir, mais ils ne verront aucune nouvelle valeur pour l'utilisation du terrain, à moins qu'ils sont recompilées. c'est vrai même si l'usage lui-même n'est pas une compilation constante expression (§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 faire référence à Flag.FLAG , le code pousse simplement une valeur de 1 ( true ) sur la pile (instruction #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
46
répondu erickson 2018-06-05 17:32:08

Un peu de curiosité de la Java Language Specification, chapitre 17, section 17.5.4 "protégé en Écriture Champs":

normalement, un champ final et statique ne peut pas être modifié. Cependant, Le Système.Dans, Système.out et System.les err sont des champs finaux statiques qui, pour des raisons d'héritage, doit être autorisé à être changé par les méthodes Système.setIn, système.sera, et du Système.setErr. Nous nous référons à ces champs comme étant protégés en écriture pour les distinguer d'ordinaire final champs.

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

12
répondu Stephan Markwalder 2012-11-21 19:21:18

j'ai également intégré avec joor bibliothèque

il suffit d'utiliser

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

J'ai aussi corrigé un problème avec override que les solutions précédentes semblent manquer. Cependant, utilisez ceci très soigneusement, seulement quand il n'y a pas d'autre bonne solution.

4
répondu iirekm 2016-08-31 01:06:32

en cas de présence d'un responsable de la sécurité, on peut utiliser AccessController.doPrivileged

selon le même exemple tiré 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é à:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});
4
répondu VanagaS 2017-03-25 06:06:43

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

4
répondu nndru 2017-06-07 10:32:39

la réponse acceptée a fonctionné pour moi jusqu'à mon déploiement sur JDK 1.8u91. Puis j'ai réalisé qu'il échouait à la ligne field.set(null, newValue); quand j'avais lu la valeur par réflexion avant d'appeler la méthode setFinalStatic .

probablement la lecture a causé une configuration quelque peu différente de Java reflection internals (à savoir sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl dans le cas de l'échec au lieu de sun.reflect.UnsafeStaticObjectFieldAccessorImpl dans le cas du succès) mais je ne l'ai pas développé plus loin.

depuis que j'ai dû Temporairement définir une nouvelle valeur basé sur l'ancienne valeur et plus tard remettre l'ancienne valeur en arrière, j'ai changé la signature petit bit pour fournir la fonction de calcul externe et aussi retourner 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;
    } ...

Toutefois, pour le cas général, cela ne serait pas suffisant.

2
répondu Tomáš Záluský 2017-05-10 12:45:19

vient de voir cette question sur l'une des questions d'entrevue, si possible pour changer la variable finale avec réflexion ou dans runtime. Je me suis vraiment intéressé, de sorte que ce que je suis devenu avec:

 /**
 * @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();
    }
}

une classe simple avec la variable finale String. Donc dans la classe principale importer java.lang.refléter.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);
    }
}

la sortie sera la suivante:

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

selon la documentation https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html

-1
répondu hasskell 2016-03-04 09:21:27

le point entier d'un champ final est qu'il ne peut pas être réassigné une fois défini. La JVM utilise cette garantie pour maintenir la cohérence à différents endroits (par exemple, les classes internes faisant référence à des variables externes). Donc pas. Le fait de pouvoir le faire briserait la co-entreprise!

la solution n'est pas de le déclarer final en premier lieu.

-5
répondu thecoop 2010-07-21 16:38:55