La modification finale champs en Java
Nous allons commencer avec un simple cas de test:
import java.lang.reflect.Field;
public class Test {
private final int primitiveInt = 42;
private final Integer wrappedInt = 42;
private final String stringValue = "42";
public int getPrimitiveInt() { return this.primitiveInt; }
public int getWrappedInt() { return this.wrappedInt; }
public String getStringValue() { return this.stringValue; }
public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException {
Field field = Test.class.getDeclaredField(name);
field.setAccessible(true);
field.set(this, value);
System.out.println("reflection: " + name + " = " + field.get(this));
}
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
Test test = new Test();
test.changeField("primitiveInt", 84);
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
test.changeField("wrappedInt", 84);
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
test.changeField("stringValue", "84");
System.out.println("direct: stringValue = " + test.getStringValue());
}
}
Quiconque le soin de deviner ce qui va être imprimé en sortie (indiqué en bas pour ne pas gâcher la surprise immédiatement).
Les questions sont:
- Pourquoi primitive et enveloppé entier se comporter différemment?
- Pourquoi ne réfléchissant vs un accès direct retour des résultats différents?
- L'un des fléaux qui m'a le plus - pourquoi Chaîne se comportent comme des primitifs
int
et non pas commeInteger
?
Résultats (java 1.5):
reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
- cela peut étroitement liés à la compilateur.
Vous devez vous connecter pour publier un commentaire.
Des constantes de compilation sont inline (à javac au moment de la compilation). Voir la JLS, en particulier 15.28 définit une expression constante et 13.4.9 traite de la compatibilité binaire ou finale, les champs et les constantes.
Si vous faites le domaine de la non-définitive ou d'attribuer un non-compiler constante de temps, la valeur n'est pas insérée. Par exemple:
private final String stringValue = null!=null?"": "42";
À mon avis, c'est encore pire: Un collègue m'a fait à la suite de drôle de chose:
En faisant cela, vous pouvez modifier le comportement de l'ensemble de la JVM vous êtes en cours d'exécution.
(bien sûr, vous ne pouvez modifier que les valeurs pour les valeurs entre -127 et 127)
Réflexion du
set(..)
méthode fonctionne avecFieldAccessor
s.Pour
int
il obtient unUnsafeQualifiedIntegerFieldAccessorImpl
, dont la super-classe définit lareadOnly
bien pour être vrai seulement si le champ est les deuxstatic
etfinal
Donc d'abord répondre à la question non formulée - voici pourquoi la
final
est changé, sans exception.Toutes les sous-classes de
UnsafeQualifiedFieldAccessor
utiliser lesun.misc.Unsafe
classe pour obtenir les valeurs. Les méthodes y sont tousnative
, mais leurs noms sontgetVolatileInt(..)
etgetInt(..)
(getVolatileObject(..)
etgetObject(..)
respectivement). Ladite accesseurs utiliser le "volatile", version. Voici ce qui se passe si l'on ajoute le non-volatile version:(où
unsafe
est instancié par la réflexion, il n'est pas permis autrement)(et j'appelle
getObject
pourInteger
etString
)Qui donne des résultats intéressants:
À ce point, je me souviens un article à javaspecialists.l'ue de discuter d'un sujet connexe. Il cite La JSR-133:
Le chapitre 9 décrit les détails observés dans cette question.
Et il s'avère que ce comportement n'est pas inattendu, car les modifications de
final
champs sont censé arriver juste après l'initialisation de l'objet.wrappedInt
vous pouvez utilisergetObject
au lieu degetInt
Ce n'est pas une réponse, mais il apporte un autre point de prêter à confusion:
Je voulais voir si le problème a été au moment de la compilation d'évaluation ou si la réflexion était en fait Java permettant d'obtenir autour de la
final
mot-clé. Voici un programme de test. Tout ce que je est un autre jeu de getter appels, donc il y a un avant et après chaquechangeField()
appel.Ici la sortie-je obtenir (sous Eclipse, Java 1.6)
Pourquoi diable ne l'appel direct à getWrappedInt() changement ?
Il y a un travail autour de cela. si vous définissez la valeur de la private static final déposé dans la statique {} bloc il va fonctionner car il ne sera pas inline le champ: