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);
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
booleanvaleurstrueetfalsedansmainsont autoboxed de référence de typeBoolean"" constantesBoolean.TRUEetBoolean.FALSE - la réflexion est utilisée pour remplacer
public static final Boolean.FALSEparBooleanvisé parBoolean.TRUE - en conséquence, par la suite chaque fois qu'un
falseest autoboxé àBoolean.FALSE, il se réfère à la mêmeBooleancomme celle visée parBoolean.TRUE - Tout ce qui était
"false"est maintenant"true"1519590920"
questions connexes
- utilisant la réflexion pour changer
static final File.separatorCharpour les essais unitaires - comment limiter l'accès aux utilisations "légitimes"?
- A des exemples de jouer avec
Integer's cache, la mutation d'unString, etc
- A des exemples de jouer avec
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
finald'un objet après sa construction. Les champsfinalpeuvent ê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 champsfinalde l'objet sont mis à jour. L'objet ne doit pas être rendu visible à d'autres fils, ni le Les champsfinaldoivent être lus jusqu'à ce que toutes les mises à jour des champsfinalde l'objet soient terminées. Les gels d'un champfinalse produisent à la fois à la fin du constructeur dans lequel le champfinalest placé et immédiatement après chaque modification d'un champfinalpar réflexion ou autre mécanisme spécial.Même alors, il ya un certain nombre de complications. Si un
finalchamp est initialisé à une constante de compilation dans le domaine déclaration, les changements dans le champfinalne peuvent pas être observés, puisque les utilisations de ce champfinalsont 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 champfinalavec 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
- il est peu probable que cette technique fonctionne avec une primitive
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 ?
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
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
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.
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;
});
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 .
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.
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
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.