В Java мы видим много мест, где final
ключевое слово можно использовать, но его использование встречается редко.
Например:
String str = "abc";
System.out.println(str);
В приведенном выше случае, str
может быть, final
но это обычно прекращается.
Когда метод никогда не будет переопределен, мы можем использовать ключевое слово final. Точно так же в случае класса, который не будет наследоваться.
Действительно ли использование ключевого слова final в любом из этих случаев действительно повышает производительность? Если так, то как? Пожалуйста, объясни. Если правильное использование final
действительно имеет значение для производительности, какие привычки должен разработать Java-программист, чтобы наилучшим образом использовать ключевое слово?
Ответы:
Обычно нет. Для виртуальных методов, HotSpot отслеживает , есть ли способ на самом деле переопределен, и может выполнять оптимизации, такие как встраивание, в предположении, что метод не был переопределен - до тех пор, пока он не загрузит класс, который переопределяет метод, и в этот момент он может отменить (или частично отменить) эти оптимизации.
(Конечно, это предполагает, что вы используете HotSpot - но это, безусловно, самая распространенная JVM, так что ...)
На мой взгляд, вы должны использовать
final
на основе четкого дизайна и удобочитаемости, а не по соображениям производительности. Если вы хотите что-то изменить по соображениям производительности, вам следует выполнить соответствующие измерения, прежде чем изгибать наиболее четкий код из формы - таким образом, вы можете решить, стоит ли какая-либо дополнительная достигнутая производительность в худшей читаемости / дизайне. (По моему опыту это почти никогда не стоит; YMMV.)РЕДАКТИРОВАТЬ: Поскольку последние поля были упомянуты, стоит упомянуть, что они в любом случае являются хорошей идеей с точки зрения четкого дизайна. Они также изменяют гарантированное поведение с точки зрения видимости между потоками: после завершения конструктора все конечные поля гарантированно сразу же становятся видимыми в других потоках. Это, пожалуй, наиболее распространенное использование
final
в моем опыте, хотя, как сторонник эмпирического правила Джоша Блоха "я должен использовать егоfinal
чаще для занятий ..."источник
final
обычно рекомендуется, потому что это облегчает понимание кода и помогает находить ошибки (потому что это делает намерение программистов явным). PMD, вероятно, рекомендует использоватьfinal
из-за этих проблем стиля, а не из-за соображений производительности.Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure.
. Кроме того ,An immutable object can be in exactly one state, the state in which it was created.
противMutable objects, on the other hand, can have arbitrarily complex state spaces.
. Исходя из моего личного опыта, использование ключевого словаfinal
должно подчеркнуть намерение разработчика склоняться к неизменности, а не к «оптимизации» кода. Я призываю вас прочитать эту главу, увлекательно!final
ключевого слова для переменных может уменьшить количество байт-кода, что может повлиять на производительность.Короткий ответ: не беспокойся об этом!
Длинный ответ:
Когда говорим об окончательных локальных переменных имейте в виду, что использование ключевого слова
final
поможет компилятору статически оптимизировать код , что в конечном итоге может привести к более быстрому коду. Например, последние строкиa + b
в примере ниже сцепляются статически (во время компиляции).Результат?
Протестировано на Java Hotspot VM 1.7.0_45-b18.
Так сколько же фактическое улучшение производительности? Я не смею говорить. В большинстве случаев, вероятно, маргинальный (~ 270 наносекунд в этом синтетическом тесте, потому что объединение строк вообще исключается - редкий случай), но в сильно оптимизированном коде утилит это может быть фактором. В любом случае ответ на первоначальный вопрос - да, это может улучшить производительность, но в лучшем случае незначительно .
Помимо преимуществ времени компиляции, я не смог найти никаких доказательств того, что использование ключевого слова
final
оказывает какое-либо измеримое влияние на производительность.источник
String
конкатенация - намного более дорогая операция, чем сложениеIntegers
. Делать это статически (если возможно) более эффективно, это то, что показывает тест.Да, оно может. Вот пример, где final может повысить производительность:
Условная компиляция - это метод, при котором строки кода не компилируются в файл класса на основании определенного условия. Это можно использовать для удаления тонны кода отладки в производственной сборке.
учтите следующее:
Преобразовав атрибут doSomething в последний атрибут, вы сказали компилятору, что всякий раз, когда он видит doSomething, он должен заменить его на false в соответствии с правилами подстановки во время компиляции. Первый проход компилятора изменяет код на что-то вроде этого:
Как только это будет сделано, компилятор еще раз посмотрит на него и обнаружит, что в коде есть недостижимые операторы. Поскольку вы работаете с высококачественным компилятором, ему не нравятся все эти недоступные байтовые коды. Таким образом это удаляет их, и вы в конечном итоге с этим:
таким образом уменьшая любые избыточные коды или любую ненужную условную проверку.
Изменить: В качестве примера, давайте возьмем следующий код:
При компиляции этого кода с Java 8 и декомпиляции с помощью
javap -c Test.class
мы получаем:Мы можем заметить, что скомпилированный код включает только не финальную переменную
x
. Это доказывает, что конечные переменные влияют на производительность, по крайней мере, для этого простого случая.источник
Согласно IBM - это не для классов или методов.
http://www.ibm.com/developerworks/java/library/j-jtp04223.html
источник
Я поражен тем, что на самом деле никто не опубликовал какой-то реальный код, который был декомпилирован, чтобы доказать, что есть хоть какая-то незначительная разница.
Для справки это было проверено на соответствие
javac
версии8
,9
и10
.Предположим, этот метод:
Компиляция этого кода , как она есть, производит точно такой же байт - код , как когда
final
бы присутствовал (final Object left = new Object();
).Но этот:
Производит:
Уход,
final
чтобы присутствовать, производит:Код в значительной степени не требует пояснений, в случае, если есть постоянная времени компиляции, он будет загружен непосредственно в стек операндов (он не будет сохранен в массив локальных переменных, как в предыдущем примере через
bipush 12; istore_0; iload_0
), что имеет смысл так как никто не может изменить это.С другой стороны, почему во втором случае компилятор не выдает,
istore_0 ... iload_0
это за мной, это не похоже на этот слот0
используется каким-либо образом (он может уменьшить массив переменных таким образом, но может быть, я пропускаю некоторые внутренние детали, не могу расскажу наверняка)Я был удивлен, увидев такую оптимизацию, учитывая, как это
javac
делают маленькие . Что мы должны всегда использоватьfinal
? Я даже не собираюсь писатьJMH
тест (который я хотел изначально), я уверен, что различия в порядкеns
(если это вообще возможно). Единственное место, где это может быть проблемой, - это когда метод не может быть встроен из-за его размера (и объявленияfinal
уменьшит этот размер на несколько байтов).Есть еще два
final
вопроса, которые необходимо решить. Во-первых, когда методfinal
(сJIT
точки зрения), такой метод является мономорфным - и это самые любимые изJVM
.Тогда есть
final
переменные экземпляра (которые должны быть установлены в каждом конструкторе); они важны, так как они гарантируют правильно опубликованную ссылку, о которой мы немного поговорили, а также указано именноJLS
.источник
javac -g FinalTest.java
), декомпилировал код с использованиемjavap -c FinalTest.class
и не получил те же результаты (сfinal int left=12
, я получилbipush 11; istore_0; bipush 12; istore_1; bipush 11; iload_1; iadd; ireturn
). Так что да, сгенерированный байт-код зависит от множества факторов, и трудно сказать, оказывает лиfinal
это влияние на производительность или нет. Но так как байт-код отличается, может существовать некоторая разница в производительности.Вы действительно спрашиваете о двух (как минимум) разных случаях:
final
для локальных переменныхfinal
для методов / классовДжон Скит уже ответил 2). О 1):
Я не думаю, что это имеет значение; для локальных переменных компилятор может определить, является ли переменная конечной или нет (просто проверяя, назначена ли она более одного раза). Поэтому, если компилятор хочет оптимизировать переменные, которые назначаются только один раз, он может это сделать независимо от того, объявлена ли переменная на самом деле
final
или нет.final
мощь иметь значение для полей защищенного / публичного класса; там компилятору очень трудно узнать, задано ли поле более одного раза, как это может происходить из другого класса (который, возможно, даже не был загружен). Но даже тогда JVM могла бы использовать технику, описанную Джоном (оптимистично оптимизировать, вернуться, если загружен класс, который меняет поле).Таким образом, я не вижу никаких причин, почему это должно помочь производительности. Так что этот вид микрооптимизации вряд ли поможет. Вы можете попробовать сравнить его, чтобы убедиться, но я сомневаюсь, что это будет иметь значение.
Редактировать:
На самом деле, согласно ответу Тимо Весткемпера, в некоторых случаях
final
можно повысить производительность полей классов. Я исправлен.источник
final
, чтобы убедиться, что вы не назначаете ее дважды. Насколько я вижу, ту же логику проверки можно (и, вероятно, использовать) использовать для проверки, может ли быть переменнаяfinal
.javac
лучше оптимизировать. Но это всего лишь домыслы.final
переменная имеет примитивный тип или типString
и ей немедленно присваивается константа времени компиляции, как в примере вопроса, компилятор должен встроить ее, поскольку переменная является константой времени компиляции для спецификации. Но для большинства случаев использования код может выглядеть иначе, но все равно не имеет значения, является ли константа встроенной или считываемой из локальной переменной с точки зрения производительности.Примечание: не эксперт по Java
Если я правильно запомнил java, было бы очень мало способов улучшить производительность, используя ключевое слово final. Я всегда знал, что он существует для «хорошего кода» - дизайна и читабельности.
источник
Я не эксперт, но я полагаю, вам следует добавить
final
ключевое слово в класс или метод, если оно не будет перезаписано, и оставить переменные в покое. Если будет какой-либо способ оптимизировать такие вещи, компилятор сделает это за вас.источник
На самом деле, во время тестирования некоторого кода, связанного с OpenGL, я обнаружил, что использование модификатора final в приватном поле может снизить производительность. Вот начало класса, который я тестировал:
И этот метод я использовал для тестирования производительности различных альтернатив, среди которых класс ShaderInput:
После третьей итерации с разогретой виртуальной машиной я постоянно получал эти цифры без последнего ключевого слова:
С последним ключевым словом:
Обратите внимание на цифры для теста ShaderInput.
Не имело значения, сделал ли я поля открытыми или закрытыми.
Кстати, есть еще несколько сбивающих с толку вещей. Класс ShaderInput превосходит все другие варианты, даже с ключевым словом final. Это замечательно, потому что это класс, обертывающий массив с плавающей точкой, в то время как другие тесты напрямую манипулируют массивом. Надо понять это. Может иметь отношение к свободному интерфейсу ShaderInput.
Также System.arrayCopy, по-видимому, на самом деле несколько медленнее для небольших массивов, чем простое копирование элементов из одного массива в другой в цикле for. И использование sun.misc.Unsafe (а также прямой java.nio.FloatBuffer, не показанный здесь) работает ужасно.
источник
Final (по крайней мере, для переменных и параметров членов) больше для людей, чем для машины.
Хорошей практикой является сделать переменные окончательными, где это возможно. Я бы хотел, чтобы Java по умолчанию делала final с переменными и имела ключевое слово Mutable, чтобы разрешить изменения. Неизменяемые классы приводят к гораздо лучшему многопоточному коду, и просто взгляд на класс с «final» перед каждым членом быстро покажет его неизменность.
Другой случай - я конвертировал много кода для использования аннотаций @ NonNull / @ Nullable (Вы можете сказать, что параметр метода не должен быть нулевым, тогда IDE может предупреждать вас всякий раз, когда вы передаете переменную, которая не помечена @ NonNull - все это распространяется до нелепой степени). Гораздо проще доказать, что переменная-член или параметр не могут иметь значение null, если они помечены как final, поскольку вы знаете, что они не будут переназначаться где-либо еще.
Мое предложение состоит в том, чтобы привыкнуть применять final для элементов и параметров по умолчанию. Это всего лишь несколько символов, но это подтолкнет вас к улучшению стиля кодирования, если ничего больше.
Final для методов или классов - это еще одна концепция, поскольку она запрещает очень правильную форму повторного использования и на самом деле мало что говорит читателю. Лучшее использование, вероятно, - это то, как они сделали String и другие встроенные типы окончательными, чтобы вы могли повсеместно полагаться на согласованное поведение - это предотвратило много ошибок (хотя бывали случаи, когда я ЛЮБИЛ, чтобы расширять строку .... о возможности)
источник
Как упоминалось в другом месте, 'final' для локальной переменной и, в несколько меньшей степени, переменной-члена, больше зависит от стиля.
'final' - это утверждение, что вы намерены изменить переменную (т. е. переменная не будет меняться!). Затем компилятор может помочь вам, подав жалобу, если вы нарушаете собственное ограничение.
Я разделяю мнение, что Java был бы лучшим языком, если бы идентификаторы (извините, я просто не могу назвать неизменную вещь «переменная») были окончательными по умолчанию и требовали от вас явного указания, что они являются переменными. Но, сказав это, я обычно не использую 'final' для локальных переменных, которые инициализируются и никогда не назначаются; это просто кажется слишком шумным.
(Я использую final для переменных-членов)
источник
Члены, объявленные с помощью final, будут доступны по всей программе, потому что, в отличие от нефинальных, если эти члены не использовались в программе, сборщик мусора не позаботится о них, что может вызвать проблемы с производительностью из-за плохого управления памятью.
источник
final
Ключевое слово может быть использовано в Java пятью способами.Класс final: класс final означает, что мы не можем расширяться или наследование означает, что наследование невозможно.
Точно так же - объект является конечным: некоторое время мы не изменяли внутреннее состояние объекта, поэтому в таком случае мы можем указать, что объект является конечным объектом. Объект final означает, что переменная также не окончательная.
Как только ссылочная переменная становится окончательной, она не может быть переназначена другому объекту. Но может изменить содержимое объекта, пока его поля не являются окончательными
источник
final
значит, но влияет ли использованиеfinal
на производительность.