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);
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:
boolean
, les valeurs true
et false
dans main
, sont associées au type de référence Boolean
"constantes" Boolean.TRUE
et Boolean.FALSE
.public static final Boolean.FALSE
en référence à la Boolean
à laquelle se réfère le Boolean.TRUE
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."false"
est maintenant "true"
static final File.separatorChar
pour les tests unitairesInteger
, de muter une String
, etc.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 champsfinal
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 champsfinal
de l'objet sont mis à jour. L'objet ne doit pas être rendu visible par d'autres threads, ni les champsfinal
doivent être lus tant que toutes les mises à jour des champsfinal
de l'objet ne sont pas terminées. Les gels d'un champfinal
se produisent à la fin du constructeur dans lequel le champfinal
est défini et immédiatement après chaque modification d'un champfinal
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 champfinal
peuvent ne pas être observées, car les utilisations de ce champfinal
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 champfinal
avec les modifications d'un champ final qui ne se produisent pas dans le constructeur.
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.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.
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 ?
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
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
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.
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 .
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;
});
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.
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é.
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