Использование модификатора "final", когда это применимо в Java
В Java существует практика объявления каждой переменной (локальной или классовой), в качестве конечного параметра, если они действительно есть.
Хотя это делает код более многословным, это помогает легко читать / понимать код, а также предотвращает ошибки, поскольку намерение четко обозначено.
Что вы думаете об этом и что вы следуете?
25 ответов
Я думаю, что все это связано с хорошим стилем кодирования. Конечно, вы можете писать хорошие, надежные программы, не используя много final
Модификаторы везде, но когда вы думаете об этом...
Добавление final
все то, что не должно меняться, просто сужает возможности того, что вы (или следующий программист, работающий над вашим кодом) неверно истолковывают или неправильно используют мыслительный процесс, который привел к вашему коду. По крайней мере, он должен звонить в некоторые колокола, когда они теперь хотят изменить вашу ранее неизменную вещь.
Сначала это выглядит неловко, чтобы увидеть много final
ключевые слова в вашем коде, но довольно скоро вы перестанете замечать само слово и будете просто думать, что "вещь никогда не изменится с этой точки" (вы можете взять ее у меня;-)
Я думаю, что это хорошая практика. Я не использую это все время, но когда я могу и имеет смысл маркировать что-то final
Я сделаю это.
Одержимость над:
- Финальные поля - Маркировка полей как финальных заставляет их устанавливать к концу построения, делая ссылку на это поле неизменной. Это позволяет безопасно публиковать поля и может избежать необходимости синхронизации при последующих чтениях. (Обратите внимание, что для ссылки на объект неизменна только ссылка на поле - вещи, на которые ссылается ссылка на объект, все еще могут изменяться, и это влияет на неизменность.)
- Конечные статические поля - хотя я использую перечисления сейчас для многих случаев, когда я использовал статические конечные поля.
Рассмотрите, но используйте разумно:
- Заключительные занятия - дизайн Framework/API - единственный случай, когда я рассматриваю это.
- Финальные методы - в основном такие же, как и финальные Если вы используете шаблоны шаблонных методов, такие как сумасшедшие, и помечаете вещи как финальные, вы, вероятно, слишком полагаетесь на наследование и недостаточно на делегирование.
Проигнорируйте, если не чувствуете анальный
- Параметры метода и локальные переменные - я редко делаю это в основном потому, что я ленив и нахожу, что это загромождает код. Я полностью признаю, что маркировка параметров и локальных переменных, которые я не собираюсь изменять, является "правильной". Я хотел бы, чтобы это было по умолчанию. Но это не так, и я нахожу код более сложным для понимания во всех финалах. Если я нахожусь в чужом коде, я не собираюсь их извлекать, но если я пишу новый код, я не буду их вставлять. Единственным исключением является случай, когда вам нужно пометить что-то окончательное, чтобы вы могли получить доступ к ним. это изнутри анонимного внутреннего класса.
Вы действительно должны понимать полное использование окончательного ключевого слова перед его использованием. Он может применяться к переменным, полям, методам и классам и оказывать различное влияние
Я бы порекомендовал проверить статью, связанную с ниже, для более подробной информации.
final
Модификатор, особенно для переменных, является средством, позволяющим компилятору применять соглашение, которое в целом разумно: убедитесь, что (локальная или экземплярная) переменная назначается ровно один раз (не больше, не меньше). Убедившись, что переменная определенно назначена перед ее использованием, вы можете избежать распространенных случаев NullPointerException
:
final FileInputStream in;
if(test)
in = new FileInputStream("foo.txt");
else
System.out.println("test failed");
in.read(); // Compiler error because variable 'in' might be unassigned
Предотвращая присвоение переменной более одного раза, вы препятствуете расширению границ. Вместо этого:
String msg = null;
for(int i = 0; i < 10; i++) {
msg = "We are at position " + i;
System.out.println(msg);
}
msg = null;
Вам предлагается использовать это:
for(int i = 0; i < 10; i++) {
final String msg = "We are at position " + i;
System.out.println(msg);
}
Некоторые ссылки:
- Финальный рассказ (бесплатная глава книги "Хардкор Ява")
- Некоторые окончательные модели
- Определенное назначение
Я довольно догматично объявляю каждую возможную переменную final
, Это включает в себя параметры метода, локальные переменные и редко поля значений. У меня есть три основные причины для объявления конечных переменных везде:
- Объявление намерения. Объявляя конечную переменную, я заявляю, что эта переменная предназначена для записи только один раз. Это тонкий совет другим разработчикам и большой совет компилятору.
- Применение одноразовых переменных: я верю в идею, что каждая переменная должна иметь только одну цель в жизни. Предоставляя каждой переменной только одну цель, вы сокращаете время, необходимое для поиска цели этой конкретной переменной при отладке.
- Позволяет оптимизировать: я знаю, что у компилятора были приемы повышения производительности, которые основывались на неизменности ссылки на переменную. Мне нравится думать, что некоторые из этих старых уловок производительности (или новых) будут использоваться компилятором.
Тем не менее, я думаю, что конечные классы и методы не так полезны, как ссылки на окончательные переменные. final
ключевое слово, когда используется с этими декларациями, просто создает препятствия для автоматического тестирования и использования вашего кода способами, которые вы никогда не ожидали.
У эффективной Java есть пункт, который говорит: "Пользуйся неизменными объектами". Объявление полей как окончательных поможет вам сделать несколько небольших шагов в этом направлении, но, конечно, есть гораздо больше действительно неизменяемых объектов, чем это.
Если вы знаете, что объекты являются неизменяемыми, они могут быть разделены для чтения среди многих потоков / клиентов без проблем с синхронизацией, и легче будет понять, как работает программа.
Я никогда не был в ситуации, когда окончательное ключевое слово для переменной не позволяло мне ошибиться, поэтому на данный момент я думаю, что это гигантская трата времени.
Если нет реальной причины сделать это (поскольку вы хотите сделать конкретное замечание о том, что эта переменная является окончательной), я бы предпочел не делать этого, поскольку я считаю, что код делает код менее читабельным.
Если, однако, вы не обнаружите, что это делает код труднее читать или писать дольше, то непременно сделайте это.
Редактировать: В качестве пояснения (и попытки отыграть отрицательные голоса) я не говорю, не отмечайте константы как окончательные, я говорю, что не делайте такие вещи, как:
public String doSomething() {
final String first = someReallyComplicatedExpressionToGetTheString();
final String second = anotherReallyComplicatedExpressionToGetAnother();
return first+second;
}
Это только делает код (на мой взгляд) труднее для чтения.
Также стоит помнить, что все, что нужно сделать в конце, - это помешать вам переназначить переменную, это не делает ее неизменной или что-то подобное.
Финал всегда должен использоваться для констант. Это даже полезно для кратковременных переменных (в пределах одного метода), когда правила определения переменной сложны.
Например:
final int foo;
if (a)
foo = 1;
else if (b)
foo = 2;
else if (c)
foo = 3;
if (d) // Compile error: forgot the 'else'
foo = 4;
else
foo = -1;
Похоже, один из самых больших аргументов против использования ключевого слова final заключается в том, что "оно не нужно" и "оно тратит пространство".
Если мы признаем многие преимущества "final", о которых говорилось во многих замечательных постах, и признаем, что для этого требуется больше времени на печатание и место, я бы сказал, что Java должна была сделать переменные "final" по умолчанию и требовать, чтобы все было помечено " изменяемый ", если кодер хочет, чтобы это было.
Я должен прочитать много кода для своей работы. Отсутствие final для переменных экземпляра - одна из главных вещей, которая раздражает меня и делает излишне трудным понимание кода. За мои деньги финал по локальным переменным вызывает больше беспорядка, чем ясности. Язык должен был быть разработан, чтобы сделать это по умолчанию, но мы должны жить с ошибкой. Иногда это полезно, особенно с циклами и определенным присваиванием с деревом if-else, но в основном это указывает на то, что ваш метод слишком сложен.
Я использую final
все время для атрибутов объекта.
final
Ключевое слово имеет семантику видимости при использовании в атрибутах объекта. По сути, установка значения атрибута final происходит до того, как конструктор вернется. Это означает, что до тех пор, пока вы не позволите this
ссылка избежать конструктора и вы используете final
для всех ваших атрибутов ваш объект (в соответствии с семантикой Java 5) гарантированно будет правильно сконструирован, и, поскольку он также является неизменяемым, его можно безопасно публиковать в других потоках.
Неизменяемые объекты - это не только безопасность потоков. Они также значительно упрощают рассуждения о переходах состояний в вашей программе, потому что пространство того, что может измениться, является преднамеренным и, если используется последовательно, полностью ограничено только тем, что должно измениться.
Я иногда также делаю методы окончательными, но не так часто. Я редко делаю уроки окончательными. Я обычно делаю это, потому что мне мало нужно. Я обычно не использую наследование много. Вместо этого я предпочитаю использовать интерфейсы и композицию объектов - это также поддается дизайну, который, как мне кажется, часто проще тестировать. Когда вы кодируете интерфейсы, а не конкретные классы, вам не нужно использовать наследование при тестировании, как и в случае с такими инфраструктурами, как jMock, гораздо проще создавать mock-объекты с интерфейсами, чем с конкретными классами.
Я думаю, что я должен сделать большинство своих уроков окончательными, но я просто еще не вошел в привычку.
Другое предостережение заключается в том, что многие люди путают окончательный вариант с тем, что содержимое переменной экземпляра не может измениться, а ссылка не может измениться.
Очевидно, чтоfinal должен использоваться для констант и для обеспечения неизменности, но есть и другое важное использование методов.
В эффективной Java есть целый пункт (пункт 15), указывающий на ловушки непреднамеренного наследования. Фактически, если вы не проектировали и не документировали свой класс для наследования, наследование от него может привести к непредвиденным проблемам (этот пример дает хороший пример). Поэтому рекомендуется использовать final для любого класса и / или метода, который не предназначен для наследования.
Это может показаться суровым, но это имеет смысл. Если вы пишете библиотеку классов для использования другими, то вы не хотите, чтобы они наследовали от вещей, которые не были предназначены для нее - вы будете привязаны к конкретной реализации класса для обратной совместимости. Если вы программируете в команде, ничто не мешает другому члену команды удалить финал, если это действительно необходимо. Но ключевое слово заставляет их задуматься о том, что они делают, и предупреждает, что класс, от которого они наследуют, не предназначен для него, поэтому им следует быть особенно осторожными.
Я настроил Eclipse, чтобы добавить final для всех полей и атрибутов, которые не были изменены. Это прекрасно работает, используя Eclipse "save actions", который добавляет эти финальные модификаторы (среди прочего) при сохранении файла.
Настоятельно рекомендуется.
Ознакомьтесь с моей записью в блоге Eclipse Save Actions.
Выбор печатать final
для каждого параметра в каждом методе будет так много раздражения как для кодеров, так и для читателей кода.
Как только раздражение выходит за рамки разумного, переключитесь на Scala, где аргументы являются окончательными по умолчанию.
Или вы всегда можете использовать инструменты для стилизации кода, которые сделают это автоматически. Все IDE имеют их реализованы или в виде плагинов.
Даже для локальных переменных знание, что оно объявлено как final, означает, что мне не нужно беспокоиться об изменении ссылки в дальнейшем. Это означает, что при отладке, и я вижу эту переменную позже, я уверен, что она ссылается на тот же объект. Это еще одна вещь, о которой мне нужно беспокоиться при поиске ошибки. Бонус заключается в том, что если 99% переменных объявлены как окончательные, то несколько переменных, которые действительно являются переменными, выделяются лучше. Кроме того, финал позволяет компилятору найти еще несколько возможных глупых ошибок, которые в противном случае могли бы остаться незамеченными.
Я уже давно пишу код и использую final, когда могу. Сделав это некоторое время (для переменных, параметров метода и атрибутов класса), я могу сказать, что 90% (или больше) моих переменных на самом деле являются окончательными. Я думаю, что преимущество НЕ в изменении переменных, когда вы этого не хотите (я видел это раньше, и иногда это больно), оплачивает дополнительную типизацию и дополнительные "окончательные" ключевые слова в вашем коде.
Тем не менее, если бы я разработал язык, я сделал бы каждую переменную окончательной, если не изменялось какое-то другое ключевое слово.
Я не использую final для классов и методов, подумал я. Это более или менее сложный выбор дизайна, если только ваш класс не является служебным классом (в этом случае у вас должен быть только один закрытый конструктор).
Я также использую Collections.unmodifiable... для создания неизменяемых списков, когда мне это нужно.
Я никогда не использую их для локальных переменных, мало смысла в добавлении многословия. Даже если вы не думаете, что переменная должна быть переназначена, это не будет иметь большого значения для следующего человека, который будет изменять этот код, который думает иначе, и, поскольку код изменяется, любая первоначальная цель сделать его окончательным может перестать быть действительным. Если это просто для ясности, я полагаю, что это терпит неудачу из-за негативных последствий многословия.
То же самое относится и к переменным-членам, так как они дают мало преимуществ, за исключением случая констант.
Это также не имеет никакого отношения к неизменности, так как лучшим показателем того, что что-то является неизменным, является то, что оно задокументировано как таковое и / или не имеет методов, которые могут изменить объект (это, наряду с завершением класса, является единственным способом гарантировать, что это неизменно).
Но эй, это только мое мнение:-)
Я редко использую final для методов или классов, потому что мне нравится позволять людям переопределять их.
В противном случае, я использую, наконец, только если это public/private static final type SOME_CONSTANT;
Для аргументов я думаю, что они не нужны. Мостли они просто повредили читабельности. Переназначение переменной-аргумента настолько безумно глупо, что я должен быть совершенно уверен, что они все равно могут рассматриваться как константы.
Тот факт, что Eclipse окрашивает последний красный цвет, облегчает поиск объявлений переменных в коде, который, как мне кажется, в большинстве случаев улучшает читаемость.
Я пытаюсь обеспечить соблюдение правила о том, что все переменные должны быть окончательными, если нет веской причины не делать этого. Гораздо проще ответить на вопрос "что это за переменная?" вопрос, если вам просто нужно найти инициализацию и быть уверенным, что это так.
Я на самом деле довольно нервничаю из-за нефинальных переменных сейчас. Это как различие между тем, чтобы нож висел на нитке над головой или просто был кухонным ящиком...
Финальная переменная - это просто хороший способ для обозначения значений.
Не финальная переменная связана с частью некоторого подверженного ошибкам алгоритма.
Одна приятная особенность заключается в том, что, когда возможность использовать переменную вне зависимости от алгоритма, большую часть времени решает вместо этого написать метод, который обычно значительно улучшает код.
Final при использовании с переменными в Java обеспечивает замену константы в C++. Поэтому, когда final и static используются для переменной, она становится неизменной. В то же время, переносимые программисты на C++ очень счастливы;-)
При использовании со ссылочными переменными он не позволяет повторно ссылаться на объект, хотя объектом можно манипулировать.
Когда final используется с методом, он не позволяет переопределять метод подклассами.
Как только использование очень ясно, это должно использоваться с осторожностью. Это в основном зависит от дизайна, так как использование метода final не поможет полиморфизму.
Его следует использовать только для переменных, если вы абсолютно уверены, что значение переменной никогда не будет изменено. Также убедитесь, что вы соблюдаете соглашение о кодировании, поддерживаемое SUN.for, например: final int COLOR_RED = 1; (Верхний регистр отделен подчеркиванием)
С помощью ссылочной переменной используйте ее только тогда, когда нам нужна неизменная ссылка на конкретный объект.
Что касается читабельности, то следует отметить, что комментарии играют очень важную роль при использовании окончательного модификатора.
Пометка класса final также может привести к тому, что некоторые привязки методов произойдут во время компиляции, а не во время выполнения. Рассмотрим "v2.foo()" ниже - компилятор знает, что B не может иметь подкласс, поэтому foo () нельзя переопределить, поэтому вызываемая реализация известна во время компиляции. Если класс B НЕ помечен как final, то возможно, что фактическим типом v2 является некоторый класс, который расширяет B и переопределяет foo().
class A {
void foo() {
//do something
}
}
final class B extends A {
void foo() {
}
}
class Test {
public void t(A v1, B v2) {
v1.foo();
v2.foo();
}
}
Я использую его для констант внутри и снаружи методов.
Я только иногда использую его для методов, потому что я не знаю, не хочет ли подкласс переопределить данный метод (по каким-либо причинам).
Что касается классов, только для некоторых классов инфраструктуры, я использовал последний класс.
IntelliJ IDEA предупреждает вас, если параметр функции записан внутри функции. Итак, я перестал использовать final для аргументов функции. Я не вижу их и внутри библиотеки Java Runtime.
Использование анонимных локальных классов для прослушивателей событий - таков обычный шаблон в Java. Наиболее распространенное использование ключевого слова final - убедиться, что переменные в области видимости доступны даже слушателю.
Тем не менее, если вы обнаружите, что вам необходимо добавить много заключительных утверждений в ваш код. Это может быть хорошим намеком на то, что вы делаете что-то не так.
В статье, опубликованной выше, приведен пример:
public void doSomething(int i, int j) {
final int n = i + j; // must be declared final
Comparator comp = new Comparator() {
public int compare(Object left, Object right) {
return n; // return copy of a local variable
}
};
}
Настоятельно рекомендуется использовать final для констант. Однако я бы не стал использовать его для методов или классов (или хотя бы подумать об этом некоторое время), потому что это усложняет тестирование, если не делает его невозможным. Если вам абсолютно необходимо сделать класс или метод финальными, убедитесь, что этот класс реализует некоторый интерфейс, чтобы вы могли иметь макет, реализующий тот же интерфейс.