Требует ли JLS встраивания окончательных строковых констант?

Я столкнулся с проблемой при манипулировании некоторым байт-кодом, где некоторая константа final String не была встроена в java-компилятор (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);
  }
}

Результирующий байт-код с 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

Вы можете видеть, что во второй раз, когда к ним обращаются поля ENABLED и DISABLED, компилятор не ввел свои значения (используя ldc), но вместо этого использовал getstatic для прямого доступа к полю. Тестирование его с другими компиляторами (Java 7, Eclipse) не вызывало такого же поведения, и константы всегда были вложенными.

Можно ли это считать ошибкой компилятора или разрешено ли постоянно строковые константы строк в соответствии с JLS?

Ответ 1

Да, поведение "inlining" определяется спецификацией:

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 (Определенное присвоение)).