Чи потрібно в Java використовувати "final" для параметрів та локальних даних, навіть коли мені цього не потрібно?


105

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

З іншого боку, я вважаю це набагато менш корисним для локальних користувачів та параметрів, і, як правило, я уникаю їх маркування, finalнавіть якщо вони ніколи не будуть перепризначені (за очевидним винятком, коли їх потрібно використовувати у внутрішньому класі) . Проте останнім часом я натрапив на код, який використовував остаточний, коли тільки може, і, напевно, технічно дає більше інформації.

Більше не впевнений у моєму стилі програмування, мені цікаво, які є інші переваги та недоліки застосування у finalбудь-якому місці, який є найпоширенішим стилем промисловості та чому.


Питання в стилі кодування @Amir, схоже, належать тут краще, ніж на SO, і я не могла знайти жодної політики у FAQ та мета цього сайту щодо цього. Чи можете ви, будь ласка, направити мене?
Дуб

1
@Oak У ньому написано: "Конкретна проблема програмування, алгоритми програмного забезпечення, кодування , запитай на переповнення стека"
Амір Резай

7
@Amir Я не згоден, я не бачу, як це проблема кодування. У будь-якому випадку ця дискусія тут не належить, тому я відкрив метатему з цього питання .
Дуб

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, дякую :), але хороший момент щодо коротких та чистих методів - я думаю, що якщо метод досить короткий, очевидно, що змінна не переназначається в, ще менший мотив розгляду finalключового слова.
Дуб

Хіба це не було б здорово, якби ми могли мати кінцеві параметри та локальні змінні, а ще короткий і чистий синтаксис? programmers.stackexchange.com/questions/199783/…
oberlies

7
"final не гарантує, що ви не можете змінити значення / стан (непримітивної) змінної. Тільки що ви не можете перевзначити посилання на цей об'єкт після ініціалізації." Непримітивний = посилання (єдині типи на Java - це примітивні типи та типи посилань). Значення змінної референтного типу є еталонною. Тому ви не можете перепризначити посилання = ви не можете змінити значення.
user102008

2
+1 для довідкового / штатного падіння. Зауважте, що маркування змінних остаточних може знадобитися для створення закриття в нових функціональних аспектах
Java8

Ваше порівняння є упередженим, як вказувало @ user102008. Призначення змінної не те саме, що оновлення його значення
ericn

64

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

З іншого боку, я вважаю це набагато менш корисним для локальних користувачів та параметрів, і зазвичай я уникаю відзначати їх як кінцеві, навіть якщо вони ніколи не будуть перепризначені (за очевидним винятком, коли їх потрібно використовувати у внутрішньому класі ).

Саме тому ви повинні використовувати finalключове слово. Ви заявляєте, що ВИ знаєте, що він ніколи не буде перепризначений, але ніхто інший цього не знає. Використання finalнегайно роз’єднує ваш код, який крихітний трохи більше.


8
Якщо ваш метод зрозумілий і робить лише одне, читач теж буде знати. Заключне слово лише робить код нечитабельним. А якщо не читати, то його набагато неоднозначніше
eddieferetro

4
@eddieferetro Не погоджується. Ключове слово finalвизначає наміри, що робить код більш читабельним. Крім того, як тільки ви матимете справу з кодом реального світу, ви помітите, що він рідко є незайманим і зрозумілим, і довільно додавати finals всюди, де ви зможете допомогти виявити помилки та краще зрозуміти застарілий код.
Андрес Ф.

4
Чи є "наміром", що змінна ніколи не зміниться, і що ваш код покладається на цей факт ? Або це намір "просто так трапляється, що ця змінна ніколи не змінюється, тому я відзначаю її як остаточну". Пізніше марний і, можливо, шкідливий шум. А оскільки ви, здається, виступаєте за маркування всіх місцевих жителів, ви робите пізніше. Великий -1.
user949300

2
finalзаявляє про наміри, що, як правило, є хорошою справою в коді, але це доходить за ціну додавання візуального безладу. Як і більшість речей у програмуванні, тут є компроміс: висловлюючи факт, що ваша змінна буде використана лише один раз, що вартує додаткового багатослів’я?
Крістофель

30

Одна з переваг використання final/ constде це можливо - це зменшення розумового навантаження для читача вашого коду.

Він може бути впевнений, що значення / посилання згодом ніколи не змінюється. Тому йому не потрібно звертати увагу на модифікації, щоб зрозуміти обчислення.

Я змінив свою думку з цього приводу після вивчення чистофункціональних мов програмування. Хлопчик, яке полегшення, якщо ти можеш довіряти "змінній", щоб завжди утримувати її початкове значення.


16
Ну, в Java finalне гарантує, що ви не можете змінити значення / стан (непримітивної) змінної. Тільки те, що ви не можете перепризначити посилання на цей об’єкт після ініціалізації.
Péter Török

9
Я знаю, саме тому я розмежував значення та реферат. Ця концепція є найбільш корисною в контексті незмінних структур даних та / або чисто функціональних можливостей.
LennyProgrammers

7
@ PéterTörök Це негайно корисно з примітивними типами та дещо корисно з посиланнями на об'єкти, що змінюються (принаймні ви знаєте, що ви завжди маєте справу з одним і тим же об’єктом!). Це надзвичайно корисно при роботі з кодом, призначеним бути незмінним з нуля.
Андрес Ф.

18

Я вважаю, що finalпараметри методу та локальні змінні є кодовими шумами. Декларації методів Java можуть бути досить довгими (особливо з дженериками) - більше не потрібно їх робити.

Якщо одиничні тести написані належним чином, буде визначено "шкідливий" параметр, тому насправді це не повинно бути проблемою. Візуальна чіткість важливіша, ніж уникнення можливої помилки, яка не підбирається, оскільки тести вашого блоку мають недостатнє покриття.

Такі інструменти, як FindBugs і CheckStyle, які можуть бути налаштовані на розрив збірки, якщо призначається параметри або локальні змінні, якщо ви глибоко переймаєтесь такими речами.

Звичайно, якщо вам потрібно зробити їх остаточними, наприклад, тому що ви використовуєте значення в анонімному класі, то жодних проблем - це найпростіше чисте рішення.

Крім очевидного ефекту від додавання додаткових ключових слів до ваших параметрів, і тим самим IMHO маскує їх, додавання остаточних до параметрів методу часто може зробити код у тілі методу менш читабельним, що робить код гіршим - бути "хорошим", код повинні бути максимально читаними та максимально простими. Для надуманого прикладу скажіть, що у мене є метод, який потребує невідчутливої ​​роботи.

Без final:

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

Простий. Чисто. Усі знають, що відбувається.

Тепер з 'остаточним', варіант 1:

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

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

З "остаточним", варіант 2:

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

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

З "остаточним", варіант 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насправді вас не захищає, як може здатися спочатку:

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

final не повністю захищає вас, якщо тип параметра не є примітивним або незмінним.


В останньому випадку finalнадає часткову гарантію, що ви маєте справу з тим же Dateпримірником. Деякі гарантії краще, ніж нічого (якщо що, ви сперечаєтесь про непорушні класи з самого початку!). У будь-якому випадку, на практиці велика частина сказаного повинна впливати на існуючі незмінні за замовчуванням мови, але це не так, це означає, що це не проблема.
Андрес Ф.

+1 для вказівки на ризик "при подальшому зниженні коду можна використовувати введення". І все-таки я вважаю за краще, щоб мої параметри були final, з можливістю зробити їх не остаточними для випадків, описаних вище. Просто не варто спамувати підпис ключовим словом.
maaartinus

7

Я дозволяю eclipse поставити finalперед кожною локальною змінною, оскільки я вважаю, що полегшує програму для читання. Я не роблю це з параметрами, оскільки я хочу підтримувати список параметрів якомога коротше, в ідеалі він повинен міститися в одному рядку.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.