1. Обзор
Спецификация языка Java не определяет и даже не использует термин константы времени компиляции. Однако разработчики часто используют этот термин для описания значения, которое не меняется после компиляции .
В этом руководстве мы рассмотрим различия между константой класса и константой времени компиляции. Мы рассмотрим константные выражения и посмотрим, какие типы данных и операторы можно использовать для определения констант времени компиляции. Наконец, мы рассмотрим несколько примеров, где обычно используются константы времени компиляции.
2. Константы класса
Когда мы используем термин константа в Java, большую часть времени мы имеем в виду статические
и окончательные
переменные класса. Мы не можем изменить значение константы класса после компиляции. Таким образом, все константы класса примитивного типа или String
также являются константами времени компиляции :
public static final int MAXIMUM_NUMBER_OF_USERS = 10;
public static final String DEFAULT_USERNAME = "unknown";
Можно создавать константы, которые не являются статическими
. Однако Java будет выделять память для этой константы в каждом объекте класса. Поэтому, если константа действительно имеет только одно значение, ее следует объявить статической
.
Oracle определил соглашение об именах для констант класса. Мы называем их прописными буквами, разделяя слова символами подчеркивания. Однако не все статические
и конечные
переменные являются константами. Если состояние объекта может измениться, оно не является константой:
public static final Logger log = LoggerFactory.getLogger(ClassConstants.class);
public static final List<String> contributorGroups = Arrays.asList("contributor", "author");
Хотя это постоянные ссылки, они относятся к изменяемым объектам.
3. Постоянные выражения
Компилятор Java умеет вычислять выражения, содержащие постоянные переменные и определенные операторы, во время компиляции кода :
public static final int MAXIMUM_NUMBER_OF_GUESTS = MAXIMUM_NUMBER_OF_USERS * 10;
public String errorMessage = ClassConstants.DEFAULT_USERNAME + " not allowed here.";
Подобные выражения называются константными выражениями, поскольку компилятор вычисляет их и создает одну константу времени компиляции. Как определено в спецификации языка Java, следующие операторы и выражения могут использоваться для константных выражений:
- Унарные операторы: +, -, ~, !
- Мультипликативные операторы: *, /, %
- Аддитивные операторы: +, –
- Операторы сдвига: <<, >>, >>>
- Реляционные операторы: <, <=, >, >=
- Операторы равенства: ==, !=
- Побитовые и логические операторы: &, ^, |
- Оператор условного-и и условного-или: &&, ||
- Тернарный условный оператор: ?:
- Выражения в скобках, содержащееся в которых выражение является константным выражением
- Простые имена, которые относятся к постоянным переменным
4. Компиляция и константы времени выполнения
Переменная является константой времени компиляции, если ее значение вычисляется во время компиляции. С другой стороны, постоянное значение времени выполнения будет вычисляться во время выполнения.
4.1. Константы времени компиляции
Переменная Java является константой времени компиляции, если она имеет примитивный тип или String
, объявлена как final
, инициализирована в своем объявлении и имеет константное выражение .
Строки
представляют собой особый случай по сравнению с примитивными типами, потому что они неизменяемы и живут в пуле строк
. Следовательно, все классы, работающие в приложении, могут совместно использовать строковые
значения.
Термин константы времени компиляции включает в себя константы класса, а также экземпляры и локальные переменные, определенные с использованием константных выражений:
public final int maximumLoginAttempts = 5;
public static void main(String[] args) {
PrintWriter printWriter = System.console().writer();
printWriter.println(ClassConstants.DEFAULT_USERNAME);
CompileTimeVariables instance = new CompileTimeVariables();
printWriter.println(instance.maximumLoginAttempts);
final String username = "foreach" + "-" + "user";
printWriter.println(username);
}
Только первая напечатанная переменная является константой класса. Однако все три напечатанные переменные являются константами времени компиляции.
4.2. Константы времени выполнения
Значение константы времени выполнения не может изменяться во время работы программы. Однако каждый раз, когда мы запускаем приложение, оно может иметь другое значение :
public static void main(String[] args) {
Console console = System.console();
final String input = console.readLine();
console.writer().println(input);
final double random = Math.random();
console.writer().println("Number: " + random);
}
В нашем примере печатаются две константы времени выполнения: определяемое пользователем значение и случайно сгенерированное значение.
5. Оптимизация статического кода
Компилятор Java статически оптимизирует все константы времени компиляции в процессе компиляции. Поэтому компилятор заменяет все ссылки на константы времени компиляции их фактическими значениями . Компилятор выполняет эту оптимизацию для любых классов, в которых используются константы времени компиляции.
Давайте рассмотрим пример, в котором есть ссылка на константу из другого класса:
PrintWriter printWriter = System.console().writer();
printWriter.write(ClassConstants.DEFAULT_USERNAME);
Далее мы скомпилируем класс и посмотрим на сгенерированный байт-код для двух приведенных выше строк кода:
LINENUMBER 11 L1
ALOAD 1
LDC "unknown"
INVOKEVIRTUAL java/io/PrintWriter.write (Ljava/lang/String;)V
Обратите внимание, что компилятор заменил ссылку на переменную ее фактическим значением. Следовательно, чтобы изменить константу времени компиляции, нам нужно перекомпилировать все классы, которые ее используют. В противном случае будет продолжать использоваться старое значение.
6. Варианты использования
Давайте рассмотрим два распространенных варианта использования констант времени компиляции в Java.
6.1. Заявление о переключении
При определении случаев для оператора switch нам необходимо придерживаться правил, определенных в спецификации языка Java:
- Для меток case оператора switch требуются значения, которые являются либо константными выражениями, либо константами перечисления.
- Никакие два выражения case-константы, связанные с оператором switch, не могут иметь одинаковое значение.
Причина этого в том, что компилятор компилирует операторы switch в байт -код tableswitch
или lookupswitch.
Они требуют, чтобы значения, используемые в операторе case, были как константами времени компиляции, так и уникальными :
private static final String VALUE_ONE = "value-one"
public static void main(String[] args) {
final String valueTwo = "value" + "-" + "two";
switch (args[0]) {
case VALUE_ONE:
break;
case valueTwo:
break;
}
}
Компилятор выдаст ошибку, если мы не используем постоянные значения в нашем операторе switch. Однако он примет окончательную строку
или любую другую константу времени компиляции.
6.2. Аннотации
Обработка аннотаций в Java происходит во время компиляции . По сути, это означает, что параметры аннотации могут быть определены только с использованием констант времени компиляции :
private final String deprecatedDate = "20-02-14";
private final String deprecatedTime = "22:00";
@Deprecated(since = deprecatedDate + " " + deprecatedTime)
public void deprecatedMethod() {}
Хотя в этой ситуации чаще используются константы класса, компилятор позволяет это реализовать, поскольку он распознает значения как неизменяемые константы.
7. Заключение
В этой статье мы рассмотрели термин константы времени компиляции в Java. Мы видели, что термин включает в себя класс, экземпляр и локальные переменные примитивного типа или String
, объявленные final
, инициализированные в его объявлении и определенные с помощью константного выражения .
В примерах мы видели разницу между константами времени компиляции и времени выполнения. Мы также видели, что компилятор использует константы времени компиляции для выполнения статической оптимизации кода.
Наконец, мы рассмотрели использование констант времени компиляции в операторах switch и аннотациях Java.
Как всегда, исходный код доступен на GitHub .