50 votes

Changer les 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));
 

Sortie:

 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 expliquer, s'il vous plaît? La première impression nous indique que le champ "s" privé n’a pas été modifié, comme je l’attendais. Mais si nous obtenons le champ par réflexion, le deuxième tirage indique qu'il est mis à jour.

71voto

Jiri Patera Points 1448

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

JLS 17.5.3 Modification Ultérieure de Finale Champs

Même alors, il ya un certain nombre de complications. Si un champ final est initialisé à une constante de compilation dans le champ de la déclaration, des modifications au champ ne peut pas être observé, depuis les utilisations de ce finale de champ sont remplacés au moment de la compilation avec le moment de la compilation constante.

Mais, si vous lisez le paragraphe ci-dessus, très attentivement, vous pouvez trouver un moyen de contourner ici (ensemble de la private final champ dans le constructeur plutôt que dans le domaine de définition):

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 static 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!

Espérons que cela aide un peu.

15voto

Joonas Pulakka Points 20361

Ce

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

compile réellement comme ceci:

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

C'est -à-dire que les constantes à la compilation sont en ligne. Voir cette question. Le moyen le plus simple d'éviter l'inline 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;
}
 

6voto

Bert F Points 27237

Voici un décompiler d' WithPrivateFinalField fichier de classe (je l'ai mis dans une classe séparée pour des raisons 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 l' toString() méthode, la constante utilisée à l'adresse 0 [0 ldc <String "s = I'm totally safe"> [23]] montre le compilateur déjà concaténé la chaîne de caractères littérale "s = " privé et le dernier champ, " I'm totally safe" ensemble à l'avance et stocké. La méthode toString() retournera toujours "s = I'm totally safe" indépendamment de la façon dont la variable d'instance, s changements.

1voto

Gabe Points 49718

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X