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

时间:2016-09-08 08:24:29

标签: java java-8 language-lawyer javac jls

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

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);
  }
}

使用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

您可以看到第二次访问字段ENABLEDDISABLED时,编译器没有内联其值(使用ldc),而是使用{{1}直接访问该字段。使用其他编译器(Java 7,Eclipse)测试它并没有触发相同的行为,并且常量总是内联。

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

1 个答案:

答案 0 :(得分:10)

是的,规范规定了“内联”行为:

  

13.1. The Form of a Binary

     

...

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

         

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

    ,必须永远不要观察该字段的默认初始值(如果不同于V)      

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

  2.   

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

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

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

  

4.12.4. final Variables

     

...

     

常量变量是基本类型或类型final的{​​{1}}变量,用常量表达式(§15.28)初始化。变量是否是常量变量可能会影响类初始化(§12.4.1),二进制兼容性(§13.1§13.4.9)和明确赋值(§16 (Definite Assignment) )。