JLS是否需要内联最终的String常量?

T. *_*art 11 java javac jls language-lawyer java-8

我在操作一些字节码时遇到了一个问题,其中final Stringjava编译器(Java 8)没有内联某个常量,请参阅下面的示例:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

您可以看到第二次访问字段ENABLED和时DISABLED,编译器没有内联它们的值(使用ldc),而是直接用于getstatic访问字段.使用其他编译器(Java 7,Eclipse)测试它并没有触发相同的行为,并且常量内联.

这可以被视为编译器错误,还是允许根据JLS一直内联字符串常量?

Hol*_*ger 10

是的,"内联"行为是规范要求的:

13.1.二进制形式

...

  1. 必须在编译时将对作为常量变量(§4.12.4)的字段的引用解析V为由常量变量的初始值设定项表示的值.

    如果这样的字段是static,那么在二进制文件的代码中不应该存在对该字段的引用,包括声明该字段的类或接口.这样的字段必须总是看似已经初始化(§12.4.2); V必须永远不要遵守该字段的默认初始值(如果不同).

    如果这样的字段是非字段static,那么除了包含该字段的类之外,二进制文件中的代码中不应该存在对该字段的引用.(它将是一个类而不是一个接口,因为一个接口只有static字段.)该类应该有代码来V在实例创建期间设置字段的值(第12.5节).

请注意,这是如何精确地解决您的情况:"如果这样的字段是static,那么在二进制文件的代码中不应该存在对该字段的引用,包括声明该字段的类或接口 ".

换句话说,如果您遇到编译器不遵守此问题,您会发现编译器错误.


作为附录,查找此信息的起点是:

4.12.4.最终变量

...

常量变量final原始类型或类型的变量String,其与一个常量表达式(初始化§15.28).变量是否是常量变量可能对类初始化(第12.4.1节),二进制兼容性(第13.1节,第13.4.9节)和明确赋值(第16节(定义赋值))有影响.