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
boolean
valeurstrue
etfalse
dansmain
sont autoboxed de référence de typeBoolean
"" constantesBoolean.TRUE
etBoolean.FALSE
- la réflexion est utilisée pour remplacer
public static final Boolean.FALSE
parBoolean
visé parBoolean.TRUE
- en conséquence, par la suite chaque fois qu'un
false
est autoboxé àBoolean.FALSE
, il se réfère à la mêmeBoolean
comme 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.separatorChar
pour 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
final
d'un objet après sa construction. Les champsfinal
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 champsfinal
de l'objet sont mis à jour. L'objet ne doit pas être rendu visible à d'autres fils, ni le Les champsfinal
doivent être lus jusqu'à ce que toutes les mises à jour des champsfinal
de l'objet soient terminées. Les gels d'un champfinal
se produisent à la fois à la fin du constructeur dans lequel le champfinal
est placé et immédiatement après chaque modification d'un champfinal
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 champfinal
ne peuvent pas être observés, puisque les utilisations de ce champfinal
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 champfinal
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
- 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.