web-dev-qa-db-fra.com

Modification des champs finaux privés via la réflexion

class WithPrivateFinalField {
    private final String s = "I’m totally safe";
    public String toString() {
        return "s = " + s;
    }
}
WithPrivateFinalField pf = new WithPrivateFinalField();
System.out.println(pf);
Field f = pf.getClass().getDeclaredField("s");
f.setAccessible(true);
System.out.println("f.get(pf): " + f.get(pf));
f.set(pf, "No, you’re not!");
System.out.println(pf);
System.out.println(f.get(pf));

Production:

s = I’m totally safe
f.get(pf): I’m totally safe
s = I’m totally safe
No, you’re not!

Pourquoi cela fonctionne-t-il de cette façon, pouvez-vous s'il vous plaît expliquer? La première impression nous indique que le champ "s" privé n'a pas été modifié, comme je m'y attendais. Mais si nous obtenons le champ par réflexion, la deuxième impression le montre, il est mis à jour.

50
Alexandr

Cette réponse est plus qu'exhaustif sur le sujet.

JLS 17.5.3 Modification ultérieure des champs finaux

Même alors, il y a un certain nombre de complications. Si un champ final est initialisé à une constante de temps de compilation dans la déclaration de champ, les modifications apportées au champ final peuvent ne pas être observées, car les utilisations de ce champ final sont remplacées au moment de la compilation par la constante de temps de compilation.

Mais, si vous lisez le paragraphe ci-dessus très attentivement, vous pouvez trouver un moyen de contourner ici (définissez le private final champ dans le constructeur au lieu de dans la définition de champ):

import Java.lang.reflect.Field;


public class Test {

  public static void main(String[] args) throws Exception {
    WithPrivateFinalField pf = new WithPrivateFinalField();
    System.out.println(pf);
    Field f = pf.getClass().getDeclaredField("s");
    f.setAccessible(true);
    System.out.println("f.get(pf): " + f.get(pf));
    f.set(pf, "No, you’re not!");
    System.out.println(pf);
    System.out.println("f.get(pf): " + f.get(pf));
  }

  private class WithPrivateFinalField {
    private final String s;

    public WithPrivateFinalField() {
      this.s = "I’m totally safe";
    }
    public String toString() {
      return "s = " + s;
    }
  }

}

La sortie est alors la suivante:

s = I’m totally safe
f.get(pf): I’m totally safe
s = No, you’re not!
f.get(pf): No, you’re not!

J'espère que ça aide un peu.

70
Jiri Patera

Cette

class WithPrivateFinalField {
    private final String s = "I’m totally safe";
    public String toString() {
        return "s = " + s;
    }  
} 

compile en fait comme ceci:

class WithPrivateFinalField {
    private final String s = "I’m totally safe";
    public String toString() {
        return "s = I’m totally safe";
    }  
}

Autrement dit, les constantes de compilation sont alignées. Voir this question. La façon la plus simple d'éviter l'incrustation est de déclarer le String comme ceci:

private final String s = "I’m totally safe".intern();

Pour les autres types, un appel de méthode trivial fait l'affaire:

private final int integerConstant = identity(42);
private static int identity(int number) {
    return number;
}
15
Joonas Pulakka

Voici une décompilation du fichier de classe WithPrivateFinalField (je l'ai mis dans une classe séparée pour plus de simplicité):

  WithPrivateFinalField();
     0  aload_0 [this]
     1  invokespecial Java.lang.Object() [13]
     4  aload_0 [this]
     5  ldc <String "I’m totally safe"> [8]
     7  putfield WithPrivateFinalField.s : Java.lang.String [15]
    10  return
      Line numbers:
        [pc: 0, line: 2]
        [pc: 4, line: 3]
        [pc: 10, line: 2]
      Local variable table:
        [pc: 0, pc: 11] local: this index: 0 type: WithPrivateFinalField

  // Method descriptor #22 ()Ljava/lang/String;
  // Stack: 1, Locals: 1
  public Java.lang.String toString();
    0  ldc <String "s = I’m totally safe"> [23]
    2  areturn
      Line numbers:
        [pc: 0, line: 6]
      Local variable table:
        [pc: 0, pc: 3] local: this index: 0 type: WithPrivateFinalField

Remarque dans la méthode toString(), la constante utilisée à l'adresse 0 [0 ldc <String "s = I’m totally safe"> [23]] Montre que le compilateur a déjà concaténé le littéral de chaîne "s = " Et le champ final privé " I’m totally safe" ensemble à l'avance et stocké. La méthode toString () renvoie toujours "s = I’m totally safe" Quelle que soit la modification de la variable d'instance s.

6
Bert F

Étant final, le compilateur s'attendait à ce que la valeur ne change pas, il a donc probablement codé en dur la chaîne directement dans votre méthode toString.

1
Gabe