Константы и переменные времени компиляции
Документация по языку Java гласит:
Если примитивный тип или строка определены как константа и значение известно во время компиляции, компилятор заменяет имя константы везде в коде на его значение. Это называется константой времени компиляции.
Я понимаю, если у нас есть кусок кода:
private final int x = 10;
Затем компилятор заменит каждое вхождение x
в коде с литералом 10
,
Но предположим, что константа инициализируется во время выполнения:
private final int x = getX(); // here getX() returns an integer value at run-time.
Будет ли какое-либо падение производительности (каким бы незначительным оно ни было) по сравнению с постоянной времени компиляции?
Другой вопрос, является ли приведенная ниже строка кода:
private int y = 10; // here y is not final
трактуется компилятором как постоянная времени компиляции?
Наконец, что я понимаю из ответов:
final static
означает постоянную времени компиляции- просто
final
означает, что это константа, но инициализируется во время выполнения - просто
static
означает инициализированный во время выполнения - без
final
является переменной и не будет рассматриваться как константа.
Правильно ли мое понимание?
7 ответов
Постоянная времени компиляции должна быть:
- объявлен окончательным
- примитив или строка
- инициализируется в декларации
- инициализируется с постоянным выражением
Так private final int x = getX();
не является постоянным
Ко второму вопросу private int y = 10;
не является константой (в данном случае не финальной), поэтому оптимизатор не может быть уверен, что значение не изменится в будущем. Поэтому он не может оптимизировать его так же хорошо, как постоянное значение. Ответ: нет, это не обрабатывается так же, как постоянная времени компиляции.
JLS делает следующие различия между final
переменные и константы:
final
переменные
Переменная может быть объявлена
final
,final
Переменная может быть назначена только один раз. Это ошибка времени компиляции, еслиfinal
Переменная присваивается, если только она точно не назначена непосредственно перед назначением ( §16 (Определенное назначение)).Когда
final
переменная была назначена, она всегда содержит одно и то же значение. Еслиfinal
переменная содержит ссылку на объект, тогда состояние объекта может быть изменено операциями над объектом, но переменная всегда будет ссылаться на один и тот же объект. Это относится и к массивам, потому что массивы являются объектами; еслиfinal
переменная содержит ссылку на массив, тогда компоненты массива могут быть изменены операциями над массивом, но переменная всегда будет ссылаться на один и тот же массив.Пустой
final
этоfinal
переменная, в объявлении которой отсутствует инициализатор.
константы
Постоянная переменная
final
переменная примитивного типа или типаString
это инициализируется с постоянным выражением ( §15.28).
Из этого определения мы можем заметить, что константа должна быть:
- объявленный
final
- примитивного типа или типа
String
- инициализируется в своей декларации (не пустой
final
) - инициализируется с постоянным выражением
Как насчет констант времени компиляции?
JLS не содержит фразу константы времени компиляции. Однако программисты часто используют термины " постоянная времени компиляции" и "взаимозаменяемо".
Если final
переменная не соответствует критериям, изложенным выше, чтобы считаться константой, технически ее следует называть final
переменная.
Согласно JLS, не требуется, чтобы "постоянная переменная" была статической.
Таким образом, "постоянная переменная" может быть статической или нестатической (переменная экземпляра).
Но JLS предъявляет некоторые другие требования, чтобы переменная была "постоянной переменной" (помимо того, что она просто конечная):
- быть только строковым или примитивным
- только инициализированный встроенный, потому что он является окончательным, и пустой финал не допускается
- инициализируется с "константным выражением" = "константным выражением времени компиляции" (см. цитату JLS ниже)
4.12.4. окончательные переменные (JLS)
Постоянная переменная - это конечная переменная примитивного типа или типа String, которая инициализируется постоянным выражением (§15.28).
Выражение константы времени компиляции - это выражение, обозначающее значение типа примитива или String, которое не завершается внезапно и составляется с использованием только следующего:
Литералы примитивного типа и литералы типа String (§3.10.1, §3.10.2, §3.10.3, §3.10.4, §3.10.5)
Приводит к примитивным типам и приводит к типу String (§15.16)
Унарные операторы +, -, ~, и! (но не ++ или -) (§15.15.3, §15.15.4, §15.15.5, §15.15.6)
Мультипликативные операторы *, / и% (§15.17)
Аддитивные операторы + и - (§15.18)
Операторы сдвига <<, >> и >>> (§15.19)
Реляционные операторы <, <=,> и>= (но не instanceof) (§15.20)
Операторы равенства == и!= (§15.21)
Побитовые и логические операторы &, ^, и | (§15.22)
Условный оператор-&&& и оператор условного-или || (§15.23, §15.24)
Тернарный условный оператор?: (§15.25)
Выражения в скобках (§15.8.5), содержащее выражение которых является константным выражением.
Простые имена (§6.5.6.1), которые ссылаются на постоянные переменные (§4.12.4).
Квалифицированные имена (§6.5.6.2) формы TypeName . Идентификатор, который ссылается на постоянные переменные (§4.12.4).
final
Ключевое слово означает, что переменная будет инициализирована один раз и только один раз. Настоящая константа должна быть объявлена static
также. Итак, ни один из ваших примеров не рассматривается компилятором как константа. Тем не менее, последнее ключевое слово сообщает вам (и компилятору), что ваши переменные будут инициализированы только один раз (в конструкторе или буквально). Если вам нужно, чтобы их значения присваивались во время компиляции, ваши поля должны быть статическими.
На производительность это не сильно влияет, но имейте в виду, что примитивные типы являются неизменяемыми, после того, как вы создали один, он будет хранить это значение в памяти, пока сборщик мусора не удалит его. Итак, если у вас есть переменная y = 1;
а затем вы измените его на y = 2;
в памяти JVM будет иметь оба значения, но ваша переменная будет "указывать" на последнее.
private int y = 10; // здесь у не окончательный
трактуется компилятором как постоянная времени компиляции?
Нет. Это переменная экземпляра, созданная, инициализированная используемая во время выполнения.
private final int x = getX();
Будет вызван при первом объявлении вашего объекта. "Падение" производительности будет зависеть от getX()
но это не то, что создает узкое место.
Просто имейте в виду, что в следующем коде x не является постоянной времени компиляции:
public static void main(String[] args) {
final int x;
x= 5;
}
На некоторых машинах может быть очень небольшое падение производительности для private final int x = getX();
поскольку для этого потребуется хотя бы один вызов метода (помимо того факта, что это не константа времени компиляции), но, как вы сказали, это будет незначительным, так зачем беспокоиться?
Что касается второго вопроса: y
не является окончательным и, следовательно, не является постоянной времени компиляции, поскольку она может измениться во время выполнения.
Проще говоря, во время компиляции компилятор заменяет ссылку на фактическое указанное значение вместо использования параметра ссылки.
public static void main(String[] args) {
final int x = 5;
}
т.е. во время компиляции компилятор принимает инициализированное значение 5 непосредственно для согласования, а не использует ссылочную переменную 'x';
Каждый примитивный литерал и строковый литерал является константой времени компиляции.
См.: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html