Рубрики
Без рубрики

Ключевое слово Java final – Влияние на производительность

Автор оригинала: Daniel Strmecki.

1. Обзор

Преимущество в производительности при использовании ключевого слова final является очень популярной темой для дискуссий среди разработчиков Java. В зависимости от того, где мы его применяем, ключевое слово final может иметь различную цель и различные последствия для производительности .

В этом уроке мы рассмотрим, есть ли какие-либо преимущества в производительности от использования ключевого слова final в нашем коде. Мы рассмотрим последствия использования final для производительности на уровне переменной, метода и класса.

Наряду с производительностью мы также упомянем аспекты дизайна использования ключевого слова final . Наконец, мы порекомендуем, следует ли и по какой причине использовать его.

2. Локальные переменные

Когда final применяется к локальной переменной, ее значение должно быть присвоено ровно один раз .

Мы можем присвоить значение в окончательном объявлении переменной или в конструкторе класса. В случае, если мы попытаемся изменить конечное значение переменной позже, компилятор выдаст ошибку.

2.1. Проверка производительности

Давайте посмотрим, может ли применение ключевого слова final к нашим локальным переменным повысить производительность.

Мы будем использовать инструмент JMH для измерения среднего времени выполнения эталонного метода. В нашем тестовом методе мы выполним простую конкатенацию строк не конечных локальных переменных:

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatNonFinalStrings() {
    String x = "x";
    String y = "y";
    return x + y;
}

Далее мы повторим тот же тест производительности, но на этот раз с конечными локальными переменными:

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatFinalStrings() {
    final String x = "x";
    final String y = "y";
    return x + y;
}

JMH позаботится о выполнении итераций прогрева, чтобы позволить оптимизации компилятора JIT начать работу. Наконец, давайте взглянем на измеренные средние показатели в наносекундах:

Benchmark                              Mode  Cnt  Score   Error  Units
BenchmarkRunner.concatFinalStrings     avgt  200  2,976 ± 0,035  ns/op
BenchmarkRunner.concatNonFinalStrings  avgt  200  7,375 ± 0,119  ns/op

В нашем примере использование конечных локальных переменных позволило ускорить выполнение в 2,5 раза.

2.2. Статическая оптимизация кода

Пример конкатенации строк демонстрирует, как ключевое слово final может помочь компилятору статически оптимизировать код .

Используя не окончательные локальные переменные, компилятор сгенерировал следующий байт-код для объединения двух строк:

NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN

Добавив ключевое слово final , мы помогли компилятору сделать вывод, что результат конкатенации строк на самом деле никогда не изменится. Таким образом, компилятору удалось полностью избежать конкатенации строк и статически оптимизировать сгенерированный байт-код:

LDC "xy"
ARETURN

Следует отметить, что в большинстве случаев добавление final к нашим локальным переменным не приведет к значительному повышению производительности, как в этом примере.

3. Переменные экземпляра и класса

Мы можем применить ключевое слово final к переменным экземпляра или класса. Таким образом, мы гарантируем, что их присвоение значения может быть выполнено только один раз. Мы можем присвоить значение при объявлении переменной конечного экземпляра, в блоке инициализатора экземпляра или в конструкторе.

Переменная класса объявляется путем добавления ключевого слова static к переменной-члену класса. Кроме того, применяя ключевое слово final к переменной класса, мы определяем константу . Мы можем присвоить значение при объявлении константы или в блоке статического инициализатора:

static final boolean doX = false;
static final boolean doY = true;

Давайте напишем простой метод с условиями, которые используют эти булевы константы:

Console console = System.console();
if (doX) {
    console.writer().println("x");
} else if (doY) {
    console.writer().println("y");
}

Затем давайте удалим ключевое слово final из переменных класса boolean и сравним байт-код, сгенерированный классом:

  • Пример использования не конечных переменных класса-76 строк байт – кода
  • Пример использования конечных переменных класса (констант) – 39 строк байт-кода

Добавив ключевое слово final в переменную класса, мы снова помогли компилятору выполнить статическую оптимизацию кода . Компилятор просто заменит все ссылки на конечные переменные класса их фактическими значениями.

Однако следует отметить, что подобный пример редко используется в реальных Java-приложениях. Объявление переменных как final может оказать лишь незначительное положительное влияние на производительность реальных приложений.

4. Эффективный Финал

Термин фактически конечная переменная был введен в Java 8. Переменная фактически является окончательной, если она явно не объявлена окончательной, но ее значение никогда не изменяется после инициализации.

Основная цель эффективных конечных переменных состоит в том, чтобы позволить лямбдам использовать локальные переменные, которые явно не объявлены конечными. Однако компилятор Java не будет выполнять статическую оптимизацию кода для конечных переменных так, как это делается для конечных переменных.

5. Классы и методы

Ключевое слово final имеет другое назначение при применении к классам и методам. Когда мы применяем ключевое слово final к классу, этот класс не может быть подклассом. Когда мы применяем его к методу, этот метод не может быть переопределен.

Нет никаких сообщений о преимуществах производительности применения final к классам и методам. Кроме того, конечные классы и методы могут быть причиной больших неудобств для разработчиков, поскольку они ограничивают наши возможности повторного использования существующего кода. Таким образом, неосторожное использование final может поставить под угрозу хорошие принципы объектно-ориентированного проектирования.

Существуют некоторые веские причины для создания конечных классов или методов, такие как обеспечение неизменяемости. Однако преимущество в производительности не является веской причиной для использования final на уровнях классов и методов .

6. Производительность по сравнению с Чистый Дизайн

Помимо производительности, мы могли бы рассмотреть другие причины использования final . Ключевое слово final может помочь улучшить читаемость и понятность кода. Давайте рассмотрим несколько примеров того, как final может сообщать о выборе дизайна:

  • окончательные классы неизменны по дизайну
  • методы объявляются окончательными, чтобы предотвратить несовместимость дочерних классов
  • аргументы метода объявляются окончательными для предотвращения побочных эффектов
  • конечные переменные доступны только для чтения по дизайну

Таким образом, мы должны использовать final для передачи вариантов дизайна другим разработчикам . Кроме того, ключевое слово final , применяемое к переменным, может служить полезной подсказкой для компилятора для выполнения незначительной оптимизации производительности.

7. Заключение

В этой статье мы рассмотрели преимущества производительности при использовании ключевого слова final . В примерах мы показали, что применение ключевого слова final к переменным может оказать незначительное положительное влияние на производительность . Тем не менее, применение ключевого слова final к классам и методам не приведет к каким-либо преимуществам в производительности.

Мы продемонстрировали, что, в отличие от конечных переменных, фактически конечные переменные не используются компилятором для выполнения статической оптимизации кода. Наконец, помимо производительности, мы рассмотрели последствия применения ключевого слова final на разных уровнях.

Как всегда, исходный код доступен на GitHub .