Коли слід використовувати фінал для параметрів методу та локальних змінних?


171

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

З одного боку, компілятор може зробити деякі оптимізації, і це робить наміри програміста зрозумілішими. З іншого боку, це додає багатослівності та оптимізації можуть бути тривіальними.

Це щось, що я повинен намагатися згадати?


Ось споріднений повідомлення , щоб переглянути: stackoverflow.com/questions/137868 / ...
mattlant


1
Я подаю заявку лише тому, що я не знав, що можна використовувати final як модифікатор параметрів, перш ніж читати це. Дякую!
Кіп

Пізніше питання, але саме те саме, чому-would-one-mark-local-variables-and-method-settings-as-final?
nawfal

Відповіді:


178

Ожиріння:

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

Розгляньте, але використовуйте розумно:

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

Ігноруйте, якщо не відчуваєте себе анальним:

  • Параметри методу та локальні змінні - Я РІДНО роблю це значною мірою, тому що я лінивий і мені здається, що це забиває код. Я повністю визнаю, що параметри маркування та локальні змінні, які я не збираюсь змінювати, "правильніші". Я б хотів, щоб це було за замовчуванням. Але це не так, і мені здається, що код важче зрозуміти з усіма фіналами. Якщо я перебуваю в чужому коді, я не збираюся їх витягувати, але якщо я пишу новий код, я не ставлю їх. Один виняток - це випадок, коли вам потрібно позначити щось остаточне, щоб ви могли отримати доступ це з анонімного внутрішнього класу.

  • Редагувати: зауважте, що один із випадків використання, коли кінцеві локальні змінні насправді дуже корисні, як згадує @ adam-gent , коли значення присвоюється var у if/ elseгілках.


8
Джошуа Блох стверджує, що всі класи повинні бути визначені як остаточні, якщо вони не призначені для успадкування. Я згоден з ним; Я додаю остаточний до кожного класу, який реалізує інтерфейс (щоб створити одиничні тести). Крім того, позначте як остаточні всі захищені / класичні методи, які не збираються перекривати.
rmaruszewski

61
При всій повазі до Джоша Блоха (і це чимала сума), я не згоден із загальною справою. У випадку створення API обов’язково заблокуйте речі. Буй всередині власного коду, спорудження стін, які згодом доведеться зруйнувати, - це марна трата часу.
Алекс Міллер

9
Це, безумовно, не "марнотрата часу", тим більше, що це зовсім не витрачає часу ... У додатку я зазвичай роблю майже всі класи finalза замовчуванням. Ви можете не помітити переваги, якщо не користуєтеся справді сучасним Java IDE, хоча (тобто IDEA).
Rogério

9
IDEA провів (поза коробкою) сотні перевірок коду, і деякі з них можуть виявити невикористаний / непотрібний код у finalкласах / методах. Наприклад, якщо остаточний метод оголошує кинути вивірений виняток, але фактично його не кидає, IDEA скаже вам це, і ви можете вилучити виняток із throwsпункту. Іноді ви також можете знайти цілі методи, які не використовуються, що виявляється, коли їх неможливо змінити.
Rogério

8
@rmaruszewski Позначення класів як кінцевих запобігає більшості глузливих фреймворків не в змозі знущатися над ними, тим самим ускладнюючи тестування коду. Я став би підсумковим лише для класу, якщо було б критично важливо, щоб він не подовжувався.
Майк Риландер

44

Це щось, що я повинен намагатися зробити, щоб пам'ятати?

Ні, якщо ви використовуєте Eclipse, оскільки ви можете налаштувати Save Action для автоматичного додавання цих остаточних модифікаторів для вас. Тоді ви отримуєте переваги за менші зусилля.


3
Чудова порада з Save Action, не знала про це.
санітарність

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

4
Це справді проблема для вас? Як часто у вас насправді виникали помилки, які були наслідком цього?
Алекс Міллер

1
+1 за корисну пораду в Eclipse. Я думаю, що ми повинні використовувати фінал якомога більше, щоб уникнути помилок.
смарагдовий

Чи можете ви це зробити і в IntelliJ?
Корай Тугай

15

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

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

Позначення змінної "остаточної" (і призначення її в конструкторі) корисно при введенні залежності. Він вказує на "співпрацюючий" характер змінної.

Позначення методу "остаточним" корисно в абстрактних класах. Він чітко окреслює, де знаходяться точки розширення.


11

Я finalвесь час використовую, щоб зробити Java більш вираженою. Див. Умови Java (if,else,switch ) - це не вираз, який я завжди ненавидів, особливо якщо ви звикли до функціонального програмування (наприклад, ML, Scala або Lisp).

Таким чином, ви повинні намагатися завжди (IMHO) використовувати остаточні змінні при використанні умов.

Дозвольте навести приклад:

    final String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
        default:
            throw new IllegalStateException();
    }

Тепер, якщо додати ще одне caseтвердження і не встановити, nameкомпілятор не вдасться. Компілятор також вийде з ладу, якщо ви не будете порушувати кожен випадок (що ви встановили змінну). Це дозволяє зробити Java дуже схожим на letвирази Lisp і робить її таким чином, щоб ваш код не був масовим відступом (через лексичні змінні масштабування).

І як зауважив @Recurse (але, мабуть, -1 я), ви можете зробити попереднє, створивши String name finalпомилку компілятора (яку я ніколи не сказав, що ви не могли), але ви можете легко змусити помилку компілятора відійти від встановлення імені після перемикання оператор, який викидає семантику виразів або гірше забувши, до breakякої ви не можете викликати помилку (незважаючи на те, що говорить @Recurse), не використовуючи final:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            //break; whoops forgot break.. 
            //this will cause a compile error for final ;P @Recurse
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

Через назву настройки помилки (окрім забуття до breakякої ще й іншої помилки) я зараз випадково можу це зробити:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        //should have handled all the cases for pluginType
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

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

Викладене вище в OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

let name = match p with
    | CandidateExport -> "Candidate Stuff"
    | JobPostingImport -> "Blah" ;;

У match ... with ...оцінює як функція , тобто вираження. Зауважте, як це виглядає наша заява перемикача.

Ось приклад в схемі (ракетка або курка):

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))

1
За винятком того, що компілятор java вже дасть вам помилку "ім'я, потенційно неініціалізовану", і відмовиться компілювати з фіналом або без нього.
Повторити

4
Так, але з фіналом ви можете скинути його в будь-який час. Я насправді бачив це, коли розробник перетворився else if (...)на a if(...)і таким чином скинув змінну. Я показав йому, що ніколи не сталося б з остаточною змінною. В основному finalзмушує вас призначити змінну раз і лише один раз ... так: P
Адам Гент

9

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

Тоді ви можете виправити деякі помилки, замінивши повторно використані змінні на нові змінні, оголошені ближче до точки використання. Тоді ви виявите, що зможете обгортати цілі частини методу в обрисі брекетів, і раптом ви один IDE натискаєте клавішу від "Методу вилучення", і ваш монстр просто зрозумів.

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


1
Дуже вірний останній пункт, хоча я давно відмовився від межі 80 символів, оскільки роздільна здатність екрана дещо змінилася за останні 10 років. Я можу легко помістити лінійку 300 знаків на екрані без прокрутки. Тим не менш, читабельність, звичайно, краща без finalпопереднього параметра.
бримбір

8

Ну, все це залежить від вашого стилю ... Якщо ви НЕ любите бачити фінал, коли ви не будете змінювати змінну, тоді використовуйте її. Якщо ви НЕ ХОЧУТЕ бачити це ... тоді залишайте його.

Мені особисто подобається якомога менше багатослівностей, тому я схильний уникати використання зайвих ключових слів, які насправді не потрібні.

Хоча я віддаю перевагу динамічним мовам, тому, мабуть, не дивно, що мені подобається уникати багатослів’я.

Отже, я б сказав, просто виберіть напрямок, до якого ви схиляєтесь, і просто рухайтеся з ним (незалежно від випадку, намагайтеся бути послідовним).


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


6

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

 public int processSomethingCritical( final int x, final int y ){
 // hundreds of lines here 
     // for loop here...
         int x2 = 0;
        x++; // bug aarrgg...
 // hundreds of lines there
 // if( x == 0 ) { ...

 }

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


2
Цей метод має більш серйозні проблеми, ніж відсутній фінал. Досить рідко, хоча і неможливо, що є вагома причина, щоб метод був так захаращений, що траплялися б подібні помилки. Невелика думка, введена в назви змінних, пройде довгий шлях до подібних аварій.
йкаганович

2
Якщо у вас є "сотні рядків коду" в одному методі, ви можете розбити його на кілька менших методів.
Стів Куо

5

Якщо ви пишете заявку, що комусь доведеться прочитати код після скажімо 1 року, то так, використовуйте Final для змінної, яка не повинна постійно змінюватися. Роблячи це, ваш код стане більш «самодокументованим», і ви також зменшите шанс для інших розробників робити нерозумні речі, як-от використання локальної константи як локальної тимчасової змінної.

Якщо ви пишете якийсь викинутий код, то, ну, не поспішайте визначити всі постійні та зробити їх остаточними.


4

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


2

З питання, чи це очевидно, незрозуміло, але остаточний параметр методу впливає лише на суть методу. Він НЕ передає користувачеві ніякої цікавої інформації про наміри методу. Об'єкт, що передається, все ще може бути вимкнено у межах методу (остаточне значення не є consts), а область змінної знаходиться в межах методу.

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

Наприклад, змінні, я ставлю їх остаточними, якщо вони логічно постійні.


2

Існує багато застосувань для змінної final. Ось лише кілька

Кінцеві константи

 public static class CircleToolsBetter {
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) {
          return (Math.pow(radius, 2) * PI);
        }
    }

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

Кінцеві змінні

public static String someMethod(final String environmentKey) {
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  }

}

У цьому класі ви будуєте масштабну остаточну змінну, яка додає префікс до параметра EnvironmentKey. У цьому випадку остаточна змінна є остаточною лише в межах виконання, що відрізняється при кожному виконанні методу. Кожен раз, коли метод вводиться, фінал реконструюється. Як тільки вона побудована, вона не може бути змінена під час виконання методу. Це дозволяє виправити змінну в методі протягом тривалості методу. Дивись нижче:

public class FinalVariables {


  public final static void main(final String[] args) {
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  }
}

Кінцеві константи

public double equation2Better(final double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) {
  powInputValue = X * Math.sin(result); 
} else {
  inputValue = K * Math.sin(result);   // <= Compiler error   
}

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

Підсумкові колекції

Інший випадок, коли ми говоримо про колекції, вам потрібно визначити їх незмінними.

 public final static Set VALID_COLORS; 
    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }

в іншому випадку, якщо ви не встановите це як неможливий:

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

Заключні класи та Заключні методи не можна продовжувати або перезаписувати відповідно.

РЕДАКТУВАННЯ: АДРЕСАВАННЯ ПРОБЛЕМИ ЗВІТНЬОГО КЛАСУ, ЩО ВІДПОВІДАЄТЬСЯ НА РОЗКЛАДЖЕННІ:

Існує два способи зробити підсумковий клас. Перший - використовувати ключове слово final у декларації класу:

public final class SomeClass {
  //  . . . Class contents
}

Другий спосіб зробити остаточний клас - оголосити всіх його конструкторів приватними:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

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

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

На жаль, оскільки єдиний конструктор класу є приватним, розширити цей клас неможливо. Що стосується класу Test, то немає причини, щоб клас був остаточним. Тест-клас є хорошим прикладом того, як неявні підсумкові класи можуть викликати проблеми.

Тож слід відзначити його остаточним, коли ти неявно робиш фінал класу, роблячи його конструктором приватним.


1

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


1

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

Крім того, те, що ви сказали, є правильним.


Тепер java 8 пропонує гнучкість та ефективні кінцеві змінні.
Равіндра бабу

0

Використовуйте finalключове слово для змінної, якщо ви створюєте цю змінну якimmutable

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

З випуском java 8 у нас є ще одна концепція під назвою " effectively final variable". Не остаточна змінна може містити остаточну змінну.

локальні змінні, на які посилається лямбда-вираз, повинні бути остаточними або фактично завершальними

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

До Java 7 ви не можете використовувати не остаточну локальну змінну в анонімному класі, але з Java 8 ви можете

Погляньте на цю статтю


-1

Перш за все, заключне ключове слово використовується для того, щоб зробити змінну константою. Постійний означає, що він не змінюється. Наприклад:

final int CM_PER_INCH = 2.54;

Ви б оголосили змінну остаточною, тому що сантиметр на дюйм не змінюється.

Якщо ви спробуєте замінити підсумкове значення, змінна - це те, що було оголошено спочатку. Наприклад:

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

Існує помилка компіляції, яка є на зразок:

local variable is accessed from inner class, must be declared final

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

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

Це надрукує:

Hello World!
A String
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.