В Java, я должен использовать «final» для параметров и локальных, даже если мне не нужно?

105

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

С другой стороны, я считаю, что это гораздо менее полезно с локальными параметрами и параметрами, и обычно я избегаю помечать их так, как finalбудто они никогда не будут переназначены (за исключением тех случаев, когда их нужно использовать во внутреннем классе). , В последнее время, однако, я наткнулся на код, который использовал final, когда это возможно, что, я думаю, технически предоставляет больше информации.

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

дуб
источник
@ Амирские вопросы стиля кодирования, кажется, здесь относятся лучше, чем к SO, и я не смог найти никакой политики в FAQ или мета этого сайта относительно этого. Можете ли вы направить меня?
Дуб
1
@Oak В нем говорится: «Конкретные проблемы программирования, программные алгоритмы, кодирование , вопросы о переполнении стека»
Амир Резаи,
7
@ Амир Я не согласен, я не понимаю, как это проблема кодирования. В любом случае, эта дискуссия здесь не относится, поэтому я открыл мета-тему по этому вопросу .
Дуб
3
@amir, это полностью тема для этого сайта, так как это субъективный вопрос о программировании - пожалуйста, смотрите / faq
Джефф Этвуд
1
Локальные переменные: Старые компиляторы Java обращали внимание на то, объявили ли вы локальные finalили нет, и оптимизировали соответствующим образом. Современные компиляторы достаточно умны, чтобы понять это для себя. По крайней мере, на локальные переменные, finalстрого в интересах читателей. Если ваши рутины не слишком сложны, то большинство читателей-людей должны быть в состоянии понять это и для себя.
Соломон Медленный

Ответы:

67

Я использую так finalже, как вы. Для меня это выглядит излишним по локальным переменным и параметрам метода и не дает полезной дополнительной информации.

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

Более того, как вы наверняка знаете, finalне гарантирует, что вы не можете изменить значение / состояние (не примитивной) переменной. Только то, что вы не можете переназначить ссылку на этот объект после инициализации. Другими словами, он работает без проблем только с переменными примитивных или неизменных типов. Рассмотреть возможность

final String s = "forever";
final int i = 1;
final Map<String, Integer> m = new HashMap<String, Integer>();

s = "never"; // compilation error!
i++; // compilation error!
m.put(s, i); // fine

Это означает, что во многих случаях это все еще не облегчает понимание того, что происходит внутри кода, и неправильное понимание этого может на самом деле вызывать скрытые ошибки, которые трудно обнаружить.

Петер Тёрёк
источник
1
Что касается редактирования - я знаю о семантике final, спасибо :), но хорошее замечание о методах short и clean - я думаю, что если метод достаточно короткий, то очевидно, что переменная не переопределяется, есть еще менее мотив для рассмотрения finalключевого слова.
Дуб
Разве не было бы замечательно, если бы у нас были конечные параметры и локальные переменные, и все же короткий и чистый синтаксис? programmers.stackexchange.com/questions/199783/…
oberlies
7
«final не гарантирует, что вы не можете изменить значение / состояние (не примитивной) переменной. Только то, что вы не можете переназначить ссылку на этот объект после инициализации». Nonprimitive = reference (единственными типами в Java являются примитивные типы и ссылочные типы). Значение переменной ссылочного типа является ссылкой. Следовательно, вы не можете переназначить ссылку = вы не можете изменить значение.
user102008
2
+1 для ссылки / состояния ловушки. Обратите внимание, что пометка переменных final может быть необходима для создания замыканий в новых функциональных аспектах
Java8
Ваше сравнение смещено, как указано @ user102008. Присвоение переменной не совпадает с ее обновлением значения
ericn
64

Ваш стиль программирования Java и ваши мысли в порядке - не нужно сомневаться в этом.

С другой стороны, я считаю, что это гораздо менее полезно с локальными параметрами и параметрами, и обычно я избегаю помечать их как окончательные, даже если они никогда не будут переназначены (за исключением тех случаев, когда их нужно использовать во внутреннем классе). ).

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

Джонатан Ху
источник
8
Если ваш метод понятен и делает только одно, читатель тоже узнает. Последнее слово только делает код нечитаемым. И если его нечитаемо, его гораздо более неоднозначно
eddieferetro
5
@eddieferetro Не согласен. Ключевое слово finalзаявляет намерение, что делает код более читабельным. Кроме того, как только вы столкнетесь с реальным кодом, вы заметите, что он редко бывает нетронутым и ясным, а свободное добавление finals везде, где вы можете, поможет вам обнаружить ошибки и лучше понять унаследованный код.
Андрес Ф.
4
Является ли «намерение», что переменная никогда не изменится, и что ваш код опирается на этот факт ? Или намерение «так получилось, что эта переменная никогда не меняется, поэтому я отмечаю ее как окончательную». Последнее бесполезно и, возможно, вредный шум. И так как вы, кажется, выступаете за маркировку всех местных жителей, вы делаете позже. Большой -1.
user949300
2
finalзаявляет намерение, что обычно является хорошей вещью в куске кода, но оно достигается ценой добавления визуального беспорядка. Как и большинство вещей в программировании, здесь есть компромисс: выражает ли тот факт, что ваша переменная будет использоваться только один раз, если она будет стоить дополнительной детализации?
Christopheml
Визуальный беспорядок будет значительно уменьшен при использовании подсветки синтаксиса. Я отмечаю своих местных жителей, которые назначены только один раз - и которые я хочу назначить только один раз - с помощью final. Это может быть большинство местных жителей, но явно не все . Для меня не бывает "никогда не изменится". В моем мире есть 2 четко разделенных вида местных жителей. Те, которые никогда не должны меняться снова (окончательно), и те, которые должны измениться в результате того, что диктует задача (которых очень мало, что делает функции довольно легко рассуждать).
blubberdiblub
31

Одним из преимуществ использования final/ constвезде, где это возможно, является то, что это уменьшает умственную нагрузку для читателя вашего кода.

Он может быть уверен, что значение / ссылка никогда не изменится в дальнейшем. Поэтому ему не нужно обращать внимание на модификации, чтобы понять вычисления.

Я изменил свое мнение об этом после изучения чисто функциональных языков программирования. Мальчик, какое облегчение, если вы можете доверять «переменной», чтобы всегда держать ее начальное значение.

LennyProgrammers
источник
16
Ну, в Java finalне гарантируется, что вы не можете изменить значение / состояние (не примитивной) переменной. Только то, что вы не можете переназначить ссылку на этот объект после инициализации.
Петер Тёрёк
9
Я знаю, именно поэтому я различал ценность и ссылку. Эта концепция наиболее полезна в контексте неизменяемых структур данных и / или чистой функциональности.
LennyProgrammers
7
@ PéterTörök Это сразу же полезно для примитивных типов и несколько полезно для ссылок на изменяемые объекты (по крайней мере, вы знаете, что всегда имеете дело с одним и тем же объектом!). Это очень полезно при работе с кодом, разработанным так, чтобы быть неизменным с нуля.
Андрес Ф.
18

finalВ параметрах метода и локальных переменных я считаю шум кода. Объявления методов Java могут быть довольно длинными (особенно с обобщениями) - больше нет необходимости их делать.

Если модульные тесты написаны правильно, присваивая параметры , что является «вредным» будет подобрано, поэтому он никогда не должен фактически быть проблемой. Визуальная ясность важнее, чем избегать возможной ошибки, которая не обнаружена , потому что ваши юнит-тесты имеют недостаточный охват.

Такие инструменты, как FindBugs и CheckStyle, которые можно настроить для разрыва сборки, если присваиваются параметры или локальные переменные, если вы серьезно заботитесь о таких вещах

Конечно, если вам нужно сделать их окончательными, например, потому что вы используете значение в анонимном классе, то нет проблем - это самое простое и чистое решение.

Помимо очевидного эффекта добавления дополнительных ключевых слов к вашим параметрам и, тем самым, ИМХО их маскировки, добавление окончательных параметров метода часто может сделать код в теле метода менее читабельным, что делает код хуже - «хорошим» кодом. должно быть максимально читабельным и максимально простым. Для надуманного примера, скажем, у меня есть метод, который должен работать без учета регистра.

Без final:

public void doSomething(String input) {
    input = input.toLowerCase();
    // do a few things with input
}

Просто. Clean. Все знают, что происходит.

Теперь с «окончательным», вариант 1:

public void doSomething(final String input) {
    final String lowercaseInput = input.toLowerCase();
    // do a few things with lowercaseInput
}

Хотя создание параметров finalне позволяет кодеру добавлять код дальше от мысли о том, что он работает с исходным значением, существует равный риск того, что код, который находится ниже, может использовать inputвместо того lowercaseInput, чего он не должен и который не может защитить, потому что вы можете ' т вывести его из сферы (или даже назначить nullна inputесли бы даже помочь в любом случае).

С 'final', вариант 2:

public void doSomething(final String input) {
    // do a few things with input.toLowerCase()
}

Теперь мы просто создали еще больше шума в коде и представили снижение производительности из-за необходимости вызывать toLowerCase()n раз.

С 'final', вариант 3:

public void doSomething(final String input) {
    doSomethingPrivate(input.toLowerCase());
}

/** @throws IllegalArgumentException if input not all lower case */
private void doSomethingPrivate(final String input) {
    if (!input.equals(input.toLowerCase())) {
        throw new IllegalArgumentException("input not lowercase");
    }
    // do a few things with input
}

Поговорим о кодовом шуме. Это крушение поезда. У нас есть новый метод, обязательный блок исключений, потому что другой код может вызвать его неправильно. Дополнительные юнит-тесты для исключения. Все, чтобы избежать одной простой и ИМХО предпочтительной и безвредной линии.

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

Я действительно считаю хорошей практикой / стилем, что если вы назначаете параметр, вы делаете это каждый в начале метода, предпочтительно в первой строке или сразу после базовой проверки ввода, эффективно заменяя его для всего метода, что оказывает последовательный эффект в пределах метод. Читатели знают, что любое назначение должно быть очевидным (рядом с объявлением подписи) и в согласованном месте, что значительно смягчает проблему, которую пытается избежать добавление final. На самом деле я редко назначаю параметры, но если я это делаю, я всегда делаю это в начале метода.


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

public void foo(final Date date) {
    date.setTime(0); 
    // code that uses date
}

final не защищает вас полностью, если тип параметра не является примитивным или неизменным.

богемский
источник
В последнем случае finalдает частичную гарантию того, что вы имеете дело с одним и тем же Dateэкземпляром. Некоторые гарантии лучше, чем ничего (во всяком случае, вы спорите о неизменных классах с нуля!). В любом случае, на практике многое из того, что вы говорите, должно влиять на существующие языки по умолчанию, но это не так, что означает, что это не проблема.
Андрес Ф.
+1 за указание на риск «этот код ниже может использовать ввод». Тем не менее, я бы предпочел, чтобы мои параметры были final, с возможностью сделать их не окончательными для случаев, подобных выше. Просто не стоит спамить подпись ключевым словом.
Maaartinus
Я нахожу точку , что один может ошибочно использовать inputвместо lowercaseInputтоо. Несмотря на техническую корректность, легко обнаружить ошибку, если она вызывает ошибку, так как вы можете четко видеть различную семантику двух отдельно названных переменных в месте их использования (при условии, что вы дадите им хорошие имена). Для меня более опасно объединять два семантически разных использования отдельных значений в одну и ту же переменную и, следовательно, в одно и то же имя. Это может усложнить отслеживание ошибок, так как вам придется читать и понимать весь предыдущий код, так или иначе связанный с переменной 1.
blubberdiblub
7

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

maaartinus
источник
2
Кроме того, для параметров вы можете заставить компилятор выдавать предупреждение или ошибку, если параметр назначен.
oberlies
3
Наоборот, мне труднее читать, так как это просто загромождает код.
Стив Куо
3
@Steve Kuo: Это позволяет мне быстро найти все переменные. не большой выигрыш, но вместе с предотвращением случайных заданий это стоит 6 символов. Я был бы намного счастлив, если бы было что-то вроде varдля маркировки не финальных переменных. YMMV.
Maaartinus
1
@maaartinus Договорились о var! К сожалению, в Java это не по умолчанию, и теперь уже слишком поздно менять язык. Поэтому я готов мириться с незначительными неудобствами при написании final:)
Андрес Ф.
1
@maaartinus Согласен. Я считаю, что около 80-90% моих (локальных) переменных не нужно изменять после инициализации. Следовательно, 80-90% из них имеют finalдобавленное ключевое слово, в то время как только 10-20% потребуется var...
JimmyB