Question Le JLS nécessite-t-il l'inclusion de constantes de chaîne finales?


J'ai rencontré un problème en manipulant un bytecode, où un certain final  String La constante n'a pas été incluse par le compilateur Java (Java 8), voir l'exemple ci-dessous:

public class MyTest
{
  private static final String ENABLED  = "Y";
  private static final String DISABLED = "N";

  private static boolean isEnabled(String key) {
      return key.equals("A");
  }

  private static String getString(String key, String value) {
      return key + value;
  }

  public static void main(String[] args) throws Exception {
    String flag = getString("F", isEnabled("A") ? ENABLED : DISABLED);
    System.out.println(flag);

    String flag2 = getString("F", isEnabled("A") ? ENABLED : DISABLED);
    System.out.println(flag2);
  }
}

Bytecode résultant avec javac (1.8.0_101)

public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #8                  // String F
         2: ldc           #2                  // String A
         4: invokestatic  #9                  // Method isEnabled:(Ljava/lang/String;)Z
         7: ifeq          16
        10: getstatic     #10                 // Field ENABLED:Ljava/lang/String;
        13: goto          19
        16: getstatic     #11                 // Field DISABLED:Ljava/lang/String;
        19: invokestatic  #12                 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        22: astore_1
        23: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        26: aload_1
        27: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: ldc           #8                  // String F
        32: ldc           #2                  // String A
        34: invokestatic  #9                  // Method isEnabled:(Ljava/lang/String;)Z
        37: ifeq          46
        40: getstatic     #10                 // Field ENABLED:Ljava/lang/String;
        43: goto          49
        46: getstatic     #11                 // Field DISABLED:Ljava/lang/String;
        49: invokestatic  #12                 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        52: astore_2
        53: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        56: aload_2
        57: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        60: return

Vous pouvez voir que la deuxième fois les champs ENABLED et DISABLED sont accédés, le compilateur n'a pas intégré leurs valeurs (en utilisant ldc), mais utilisé à la place getstatic accéder directement au terrain. Le tester avec d'autres compilateurs (Java 7, Eclipse) n'a pas déclenché le même comportement et les constantes ont toujours été intégrées.

Cela peut-il être considéré comme un bogue du compilateur ou est-il permis ne pas constantes de chaîne en ligne tout le temps selon le JLS?


11
2017-09-08 08:24


origine


Réponses:


Oui, le comportement "inlining" est exigé par la spécification:

13.1. La forme d'un binaire

...

  1. Une référence à un champ qui est une variable constante (§4.12.4) doit être résolue au moment de la compilation à la valeur V désigné par l'initialiseur de la variable constante.

    Si un tel champ est static, alors aucune référence au champ ne doit être présente dans le code d'un fichier binaire, y compris la classe ou l'interface qui a déclaré le champ. Un tel champ doit toujours sembler avoir été initialisé (§12.4.2); la valeur initiale par défaut du champ (si différente de V) ne doit jamais être observé.

    Si un tel champ est nonstatic, alors aucune référence au champ ne doit être présente dans le code dans un fichier binaire, sauf dans la classe contenant le champ. (Ce sera une classe plutôt qu'une interface, car une interface a seulement static fields.) La classe doit avoir du code pour définir la valeur du champ à V lors de la création de l'instance (§12.5).

Notez que cela répond précisément à votre scénario: "Si un tel champ est static, alors aucune référence au champ ne doit être présente dans le code dans un fichier binaire, y compris la classe ou l'interface qui a déclaré le champ".

En d'autres termes, si vous rencontrez un compilateur qui n'y adhère pas, vous avez trouvé un bogue de compilation.


Comme additif, le point de départ pour trouver cette information était:

4.12.4. Variables finales

...

UNE variable constante est un final variable de type ou type primitif String qui est initialisé avec une expression constante (§15.28). Le fait qu'une variable soit une variable constante peut avoir des implications en ce qui concerne l'initialisation de la classe (§12.4.1), compatibilité binaire (§13.1, §13.4.9), et assignation définitive (§16 (Affectation définitive)).


10
2017-09-08 10:54