Question

Commençons par un cas de test simple:

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 souhaite deviner ce qui sera imprimé en sortie (indiqué en bas pour ne pas gâcher la surprise immédiatement).

Les questions sont les suivantes:

  1. Pourquoi le nombre entier primitif et le nombre entier enveloppé se comportent-ils différemment?
  2. Pourquoi l'accès réfléchi et l'accès direct donnent-ils des résultats différents?
  3. Celui qui me frappe le plus - pourquoi String se comporte-t-il comme une primitive int et non comme Integer ?

Résultats (java 1.5):

reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
Était-ce utile?

La solution

Les constantes de compilation sont en ligne (au moment de la compilation de javac). Voir JLS, en particulier 15.28 définit une expression constante et 13.4.9 traite de la compatibilité binaire ou des champs et constantes finaux.

Si vous renseignez le champ ou définissez une constante de temps de non compilation, la valeur n'est pas en ligne. Par exemple:

Chaîne finale privée stringValue = null! = null? "": "42";

Autres conseils

À mon avis, la situation est encore pire: un collègue a souligné la chose amusante suivante:

@Test public void  testInteger() throws SecurityException,  NoSuchFieldException, IllegalArgumentException, IllegalAccessException  {      
    Field value = Integer.class.getDeclaredField("value");      
    value.setAccessible(true);       
    Integer manipulatedInt = Integer.valueOf(7);      
    value.setInt(manipulatedInt, 666);       
    Integer testInt = Integer.valueOf(7);      
    System.out.println(testInt.toString());
}

En procédant ainsi, vous pouvez modifier le comportement de l’ensemble de la machine virtuelle Java que vous exécutez. (bien entendu, vous ne pouvez modifier que les valeurs comprises entre -127 et 127)

La méthode set (..) de Reflection fonctionne avec les éléments FieldAccessor .

Pour int , la valeur UnsafeQualifiedIntegerFieldAccessorImpl , dont la superclasse définit la propriété readOnly n'est vraie que si le champ est les deux statique et final

Donc, pour répondre d'abord à la question non posée - voici pourquoi le final est modifié sans exception.

Toutes les sous-classes de UnsafeQualifiedFieldAccessor utilisent la classe sun.misc.Unsafe pour obtenir les valeurs. Les méthodes disponibles sont natif , mais leur nom est getVolatileInt (..) et getInt (..) ( getVolatileObject (. .) et getObject (..) respectivement). Les accesseurs susmentionnés utilisent le paramètre "volatile". version. Voici ce qui se passe si nous ajoutons la version non volatile:

System.out.println("reflection: non-volatile primitiveInt = "
     unsafe.getInt(test, (long) unsafe.fieldOffset(getField("primitiveInt"))));

(où unsafe est instancié par réflexion - ce n'est pas autorisé autrement) (et j'appelle getObject pour entier et chaîne )

Cela donne des résultats intéressants:

reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: non-volatile primitiveInt = 84
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: non-volatile wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
reflection: non-volatile stringValue = 84

À ce stade, je me souviens de un article de javaspecialists.eu traitant d'un sujet connexe. . Il cite JSR-133 :

  

Si un champ final est initialisé avec une constante de compilation dans la déclaration de champ, les modifications apportées au champ final risquent de ne pas être observées, car les utilisations de ce dernier sont remplacées au moment de la compilation par la constante de compilation.

Le chapitre 9 traite des détails observés dans cette question.

Et il s'avère que ce comportement n'est pas si inattendu, car les modifications des champs final ne sont censées se produire que juste après l'initialisation de l'objet.

Ce n'est pas une réponse, mais cela soulève un autre point de confusion:

Je voulais voir si le problème était une évaluation au moment de la compilation ou si la réflexion permettait réellement à Java de contourner le mot clé final . Voici un programme de test. Tout ce que j’ai ajouté, c’est un autre ensemble d’appels d’obtention, il y en a donc un avant et après chaque appel changeField () .

package com.example.gotchas;

import java.lang.reflect.Field;

public class MostlyFinal {
  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 = MostlyFinal.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 {
    MostlyFinal test = new MostlyFinal();

    System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
    test.changeField("primitiveInt", 84);
    System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());

    System.out.println();

    System.out.println("direct: wrappedInt = " + test.getWrappedInt());
    test.changeField("wrappedInt", 84);
    System.out.println("direct: wrappedInt = " + test.getWrappedInt());

    System.out.println();

    System.out.println("direct: stringValue = " + test.getStringValue());
    test.changeField("stringValue", "84");
    System.out.println("direct: stringValue = " + test.getStringValue());
  }
}

Voici le résultat obtenu (sous Eclipse, Java 1.6)

direct: primitiveInt = 42
reflection: primitiveInt = 84
direct: primitiveInt = 42

direct: wrappedInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84

direct: stringValue = 42
reflection: stringValue = 84
direct: stringValue = 42

Pourquoi diable l'appel direct à getWrappedInt () change-t-il?

Il y a un moyen de contourner ce problème. si vous définissez la valeur de la finale statique privée classée dans le bloc statique {}, cela fonctionnera, car le fichier ne sera pas inséré:

private static final String MY_FIELD;

static {
    MY_FIELD = "SomeText"
}

...

Field field = VisitorId.class.getDeclaredField("MY_FIELD");

field.setAccessible(true);
field.set(field, "fakeText");
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top