Як працює "остаточне" ключове слово в Java? (Я все ще можу змінити об'єкт.)


480

На Java ми використовуємо final ключове слово зі змінними, щоб вказати його значення не змінювати. Але я бачу, що ви можете змінити значення в конструкторі / методах класу. Знову ж таки, якщо змінна - staticце помилка компіляції.

Ось код:

import java.util.ArrayList;
import java.util.List;

class Test {
  private final List foo;

  public Test()
  {
      foo = new ArrayList();
      foo.add("foo"); // Modification-1
  }
  public static void main(String[] args) 
  {
      Test t = new Test();
      t.foo.add("bar"); // Modification-2
      System.out.println("print - " + t.foo);
  }
}

Наведений вище код працює добре і помилок немає.

Тепер змініть змінну як static:

private static final List foo;

Тепер це помилка компіляції. Як це finalнасправді працює?


оскільки foo не видно - як це можна скласти?
Björn Hallström

5
@therealprashant це неправда. Приватні статичні змінні є дійсними, вони доступні за допомогою статичних методів всередині класу, в якому вони визначені. Статична змінна означає, що змінна існує один раз і не пов'язана з екземпляром класу.
mbdavis

3
@mbdavis О так! Дякую тобі. Але все ж я не видаляю коментар, щоб допомогти людям, які думають як я, і тоді ваш коментар змусить їх задуматися в правильному напрямку.
відпрацьований працівник

@therealprashant гаразд не хвилюйтесь!
mbdavis

Відповіді:


518

Ви завжди може ініціювати в finalзмінному. Компілятор гарантує, що ви можете це зробити лише один раз.

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

Java не має поняття незмінності об'єкта; це досягається ретельним проектуванням об’єкта, і це далеко не тривіальне починання.


12
спробуйте зробити t.foo = new ArrayList (); в основному методі, і ви отримаєте помилку компіляції ... посилання foo прив’язане лише до одного кінцевого об'єкта ArrayList ... він не може вказувати на будь-який інший ArrayList
Code2Interface

50
хммм. Все про довідку не має значення. Дякую!
GS

2
Я маю питання. Хтось, кого я знаю, заявив, що "остаточний", також змушує змінну зберігатись у стеку. Це правильно? Я шукав всюди і не міг знайти жодної посилання, яка могла б схвалити або відхилити цю претензію. Я шукав документи на Java та Android. Також шукали "модель пам'яті Java". Можливо, це працює так на C / C ++, але я не думаю, що це працює так на Java. Я прав?
андроїд розробник

4
@androiddeveloper Ніщо в Java не може чітко керувати розміщенням стека / купи. Більш конкретно, розміщення стека, як це вирішив компілятор HotSpot JIT, підлягає аналізу втечі , який набагато більше, ніж перевірка, чи є змінна final. Змінні об'єкти також можуть бути виділені стеком. finalПоля може допомогти в аналізі втечі, але це досить непрямий маршрут. Також зауважте, що фактично остаточні змінні мають однакове оброблення, як і те, позначені finalу вихідному коді.
Марко Топольник

5
finalприсутній у файлі класу та має значні смислові наслідки для оптимізації часу виконання. Це також може понести витрати, оскільки JLS має чітку гарантію узгодженості finalполів об'єкта. Наприклад, процесор ARM повинен використовувати явну інструкцію щодо бар'єру пам'яті на кінці кожного конструктора класу, який містить finalполя. Однак для інших процесорів це не потрібно.
Марко Топольник

574

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

import java.util.ArrayList;
import java.util.List;

class Test {
    private final List foo;

    public Test() {
        foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

    public void setFoo(List foo) {
       //this.foo = foo; Results in compile time error.
    }
}

У наведеному вище випадку ми визначили конструктор для 'Test' і дали йому метод 'setFoo'.

Про конструктор: Конструктор можна викликати лише один раз на створення об'єкта за допомогою newключового слова. Ви не можете викликати конструктор кілька разів, оскільки конструктор не призначений для цього.

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

Сценарій 1

private final List foo;  // 1

fooє змінною екземпляра . Коли ми створюємо Testоб'єкт класу, то змінна інстанція fooбуде скопійована всередині об’єкта Testкласу. Якщо ми призначимо fooвсередині конструктора, тоді компілятор знає, що конструктор буде викликаний лише один раз, тому немає проблеми з його призначенням всередині конструктора.

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

Сценарій 2

private static final List foo = new ArrayList();

fooтепер статична змінна. Коли ми створюємо екземпляр Testкласу, fooвін не буде скопійований до об’єкта, оскільки fooє статичним. Тепер fooне є самостійною властивістю кожного об'єкта. Це властивість Testкласу. Але fooїх можна побачити декількома об'єктами, і якщо кожен об'єкт, створений за допомогою newключового слова, в кінцевому підсумку викликає Testконструктор, який змінює значення в момент створення декількох об'єктів (Пам'ятайте static foo, що не копіюється в кожен об'єкт, а поділяється між декількома об'єктами .)

Сценарій 3

t.foo.add("bar"); // Modification-2

Вище Modification-2від вашого запитання. У наведеному вище випадку ви не змінюєте перший посилається об'єкт, але ви додаєте вміст, до fooякого дозволено. Компілятор скаржиться, якщо ви спробуєте призначити a new ArrayList()до fooконтрольної змінної.
Правило Якщо ви ініціалізували finalзмінну, ви не можете її змінити для посилання на інший об'єкт. (У цьому випадку ArrayList)

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


1
Просто, щоб було зрозуміло. У сценарії 2 ви говорите, що fooвін буде встановлений кілька разів, незважаючи на остаточне позначення, якщо fooвін встановлений у класі Test і створено кілька екземплярів Test?
Rawr

Не зрозумів останній рядок для сценарію 2: Але fooможе бути .. кілька об'єктів.) Це означає, що якщо я створюю декілька об'єктів одночасно, то який об’єкт ініціалізує остаточну змінну, залежить від виконання?
Saumya Suhagiya

1
Я думаю, що корисний спосіб подумати над сценарієм 3 - це те, що ви призначаєте finalадресу пам’яті, на fooяку посилається ArrayList. Ви не призначаєте finalадресу пам'яті, на яку посилається перший елемент foo(або будь-який елемент з цього питання). Тому ви не можете змінити, fooале ви можете змінити foo[0].
Пінкертон

@Rawr Сценарій 2 спричинив би помилку часу компіляції через foo = new ArrayList();- fooпосилається на статичну змінну, оскільки ми знаходимось в одному класі.
flow2k

Я розробник C ++, який вивчає Java. Чи безпечно думати finalпро змінну, таку ж, як constключове слово в C ++?
Дуг Барб’єрі

213

Заключне ключове слово має численні способи використання:

  • Остаточний клас не можна підкласифікувати.
  • Остаточний метод не може бути замінений підкласами
  • Остаточну змінну можна ініціалізувати лише один раз

Інше використання:

  • Коли анонімний внутрішній клас визначений в тілі методу, всі змінні, оголошені остаточними в межах цього методу, доступні з внутрішнього класу

Статична змінна класу буде існувати з початку JVM і повинна бути ініціалізована в класі. Повідомлення про помилку не з’явиться, якщо ви це зробите.


24
Це, безумовно, моя улюблена відповідь. Просте і просте, це я б очікував прочитати в інтернет-документах про Java.
RAnders00

Тож у статичних змінних ми можемо ініціалізувати стільки разів, скільки хочемо?
jorge saraiva

1
@jorgesaraiva так, статичні змінні не є константами.
czupe

1
@jorgesaraiva Ви можете призначити (не ініціалізувати ) staticполя (доки вони не є final) стільки разів, скільки вам потрібно. Дивіться у цій вікі про різницю між призначенням та ініціалізацією .

56

finalКлючове слово може бути витлумачено двома різними способами в залежності від того, що він використовується на:

Типи значень: для ints, doubles і т. Д. Це забезпечить неможливість зміни значення,

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

Таким чином, final List<Whatever> foo;гарантує, що fooзавжди посилається на один і той же список, але зміст зазначеного списку може змінюватися з часом.


23

Якщо ви робите fooстатичний, ви повинні ініціалізувати його в конструкторі класу (або в рядку, де ви його визначаєте), як у наступних прикладах.

Конструктор класів (не примірник):

private static final List foo;

static
{
   foo = new ArrayList();
}

В лінію:

private static final List foo = new ArrayList();

Проблема тут полягає не в тому, як finalпрацює модифікатор, а в тому, як staticпрацює модифікатор.

finalМодифікатор нав'язує ініціалізацію довідки за часом виклику конструктор Завершує (тобто ви повинні ініціалізувати його в конструкторі).

Коли ви ініціалізуєте атрибут in-line, він ініціалізується перед запуском коду, який ви визначили для конструктора, і ви отримаєте такі результати:

  • якщо foo є static, foo = new ArrayList()буде виконуватися до того , як static{}конструктор ви визначили для свого класу виконуються
  • якщо foo ні static, foo = new ArrayList()то буде виконано до запуску конструктора

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

Помилка, яку ви отримуєте у своєму коді, полягає в тому, що static{} виконується під час завантаження класу, до моменту інстанцізації об'єкта цього класу. Таким чином, ви не будете ініціалізуватись fooпри створенні класу.

Подумайте про static{}блок як конструктор для об'єкта типуClass . Тут потрібно виконати ініціалізацію static finalатрибутів класу (якщо це не зроблено вбудовано).

Бічна примітка:

The final Модифікатор запевняє константні-Несс тільки для примітивних типів і посилань.

Коли ви оголошуєте finalоб'єкт, ви отримуєте цеfinal посиланням на цей об’єкт, але сам об'єкт не є постійним.

Чого ви дійсно досягаєте при оголошенні finalатрибута, це те, що, коли ви оголосите об’єкт для вашої конкретної мети (наприклад, те, final Listщо ви оголосили), для цього буде використаний той і лише цей об'єкт: ви не зможете змінитись List fooна інший List, але ви все одно можете змінити свої дані List, додаючи / видаляючи елементи (використовувані Listвами будуть ті ж самі, лише зі зміненим вмістом).


8

Це дуже вдале запитання про інтерв'ю. Іноді вони можуть навіть запитати вас, яка різниця між кінцевим об'єктом і незмінним об'єктом.

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

2) Незаперечний об'єкт, чий статок може НЕ бути змінена, але його завдання може бути змінено. Наприклад:

    String x = new String("abc"); 
    x = "BCG";

ref змінну x можна змінити, щоб вказати інший рядок, але значення "abc" не можна змінити.

3) Змінні екземпляри (нестатичні поля) ініціалізуються при виклику конструктора. Таким чином, ви можете ініціалізувати значення для вас змінних всередині конструктора.

4) "Але я бачу, що ви можете змінити значення в конструкторі / методах класу". - Ви не можете змінити його всередині методу.

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


7

finalКлючове слово в Java використовується для обмеження користувача. finalКлючове слово java може використовуватися в багатьох контекстах. Фіналом може бути:

  1. змінна
  2. метод
  3. клас

finalКлючове слово може бути застосований зі змінними, в finalзмінну , яка не має ніякого значення, називається порожнім finalзмінної або неініціалізованих finalзмінної. Його можна ініціалізувати лише в конструкторі. Пуста finalзмінна може бути staticтакож ініціалізованою лише в staticблоці.

Кінцева змінна Java:

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

Приклад finalзмінної

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

class Bike9{  
    final int speedlimit=90;//final variable  
    void run(){  
        speedlimit=400;  // this will make error
    }  

    public static void main(String args[]){  
    Bike9 obj=new  Bike9();  
    obj.run();  
    }  
}//end of class  

Заключний клас Java:

Якщо ви робите будь-який клас таким final, ви не можете продовжити його.

Приклад підсумкового класу

final class Bike{}  

class Honda1 extends Bike{    //cannot inherit from final Bike,this will make error
  void run(){
      System.out.println("running safely with 100kmph");
   }  

  public static void main(String args[]){  
      Honda1 honda= new Honda();  
      honda.run();  
      }  
  }  

Остаточний метод Java:

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

Приклад finalметоду (run () у Honda не може перемогти run () у Bike)

class Bike{  
  final void run(){System.out.println("running");}  
}  

class Honda extends Bike{  
   void run(){System.out.println("running safely with 100kmph");}  

   public static void main(String args[]){  
   Honda honda= new Honda();  
   honda.run();  
   }  
}  

Спільний доступ: http://www.javatpoint.com/final-keyword


7

Варто згадати кілька прямих визначень:

Заняття / Методи

Ви можете оголосити деякі або всі методи класу як final, щоб вказати, що метод не може бути замінений підкласами.

Змінні

Після finalініціалізації змінної вона завжди містить те саме значення.

final в основному уникайте перезапису / переписування будь-чим (підкласи, змінна "перепризначити"), залежно від випадку.


1
Я думаю, остаточне визначення змінних трохи коротке; "У Java, коли кінцеве ключове слово використовується зі змінною примітивних типів даних (int, float, .. тощо), значення змінної неможливо змінити, але коли final використовується з непомітивними змінними (Зверніть увагу, що непомітивні змінні завжди є посиланнями на об'єкти в Java), члени згаданого об'єкта можуть бути змінені. Остаточні для непомітивних змінних означають, що вони не можуть бути змінені для позначення будь-якого іншого об'єкта ". geeksforgeeks.org/g-fact-48
ceyun

Також діє, спеціально для згадування як примітивних та непомітивних справ. Ткс.
ivanleoncz

4

final- це зарезервоване ключове слово в Java для обмеження користувача, і його можна застосувати до змінних, методів, змінних класів та локальних змін. Кінцеві змінні часто декларуються за допомогою staticключового слова на Java і розглядаються як константи. Наприклад:

public static final String hello = "Hello";

Коли ми використовуємо finalключове слово зі змінною декларацією, значення, збережене всередині цієї змінної, не може бути змінено останньою.

Наприклад:

public class ClassDemo {
  private final int var1 = 3;
  public ClassDemo() {
    ...
  }
}

Примітка : Клас, оголошений як остаточний, не може бути розширений або успадкований (тобто не може бути підкласу суперкласу). Також добре зауважити, що методи, оголошені як остаточні, не можуть бути замінені підкласами.

Переваги використання кінцевого ключового слова розглядаються в цій темі .


2
the value stored inside that variable cannot be changed latterчастково вірно. Це стосується лише примітивних типів даних. У випадку, якщо будь-який об’єкт зроблений так final, як, як і arraylist, його значення може змінюватися, але не посилатися. Дякую!
GS

3

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


2

Прочитайте всі відповіді.

Існує ще один випадок, коли finalключове слово можна використовувати, тобто в аргументі методу:

public void showCaseFinalArgumentVariable(final int someFinalInt){

   someFinalInt = 9; // won't compile as the argument is final

}

Може використовуватися для змінної, яку не слід змінювати.


1

Коли ви робите статичний остаточний, його слід ініціалізувати у блоці статичної ініціалізації

    private static final List foo;

    static {
        foo = new ArrayList();
    }

    public Test()
    {
//      foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

1

finalКлючове слово вказує на те, що змінна може бути инициализирован тільки один раз. У своєму коді ви виконуєте лише одну ініціалізацію остаточного, щоб умови були виконані. Це твердження виконує одиночну ініціалізацію foo. Зауважте, що final! = Незмінна, це означає лише, що посилання не може змінитися.

foo = new ArrayList();

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

Коли ви виконуєте свій метод за static finalсценарієм, Testклас завантажується до ініціалізації tв цей час, немає екземплярів fooзначення, яке воно не було ініціалізовано, тому fooвстановлено за замовчуванням для всіх об'єктів, які є null. У цей момент я припускаю, що ваш код кидає, NullPointerExceptionколи ви намагаєтесь додати елемент до списку.


1

Перш за все, місце у вашому коді, де ви ініціалізуєте (тобто призначаєте вперше) foo, знаходиться тут:

foo = new ArrayList();

foo - це об'єкт (з типом List), тому це посилальний тип, а не тип значення (наприклад, int). Як такий, він містить посилання на місце пам'яті (наприклад, 0xA7D2A834), де зберігаються елементи списку. Такі лінії

foo.add("foo"); // Modification-1

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

foo = new ArrayList();

Це призведе до помилки компіляції.


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

Якщо у вас НЕ є статичне ключове слово, у кожного об'єкта, який створює клас, є своя копія foo. Тому конструктор присвоює значення порожній свіжої копії змінної foo, що цілком чудово.

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


1
  1. Оскільки кінцева змінна нестатична, її можна ініціалізувати в конструкторі. Але якщо ви зробите його статичним, його не можна ініціалізувати конструктором (оскільки конструктори не є статичними).
  2. Доповнення до списку не очікується, щоб зробити список остаточним. finalпросто пов'язує посилання на конкретний об'єкт. Ви можете змінювати "стан" цього об'єкта, але не сам об'єкт.

1

Далі наведені різні контексти, де використовується фінал.

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

class Main {
   public static void main(String args[]){
      final int i = 20;
      i = 30; //Compiler Error:cannot assign a value to final variable i twice
   }
}

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

Підсумкові класи Заключний клас не можна продовжувати (успадковувати)

final class Base { }
class Derived extends Base { } //Compiler Error:cannot inherit from final Base

public class Main {
   public static void main(String args[]) {
   }
}

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

//Error in following program as we are trying to override a final method.
class Base {
  public final void show() {
       System.out.println("Base::show() called");
    }
}     
class Derived extends Base {
    public void show() {  //Compiler Error: show() in Derived cannot override
       System.out.println("Derived::show() called");
    }
}     
public class Main {
    public static void main(String[] args) {
        Base b = new Derived();;
        b.show();
    }
}

1

Я думав написати тут оновлену та глибоку відповідь.

final Ключове слово можна використовувати в декількох місцях.

  1. заняття

Це final classозначає, що жоден інший клас не може поширити цей остаточний клас. Коли Java Run Time ( JRE ) знає, що посилання на об'єкт є типом остаточного класу (скажімо, F), він знає, що значення цього посилання може бути лише у типі F.

Наприклад:

F myF;
myF = new F();    //ok
myF = someOther;  //someOther cannot be in type of a child class of F.
                  //because F cannot be extended.

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

  1. методи

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

  1. поля, локальні змінні, параметри методу

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

Наприклад:

Для полів, локальних параметрів

final FinalClass fc = someFC; //need to assign straight away. otherwise compile error.
final FinalClass fc; //compile error, need assignment (initialization inside a constructor Ok, constructor can be called only once)
final FinalClass fc = new FinalClass(); //ok
fc = someOtherFC; //compile error
fc.someMethod(); //no problem
someOtherFC.someMethod(); //no problem

Для параметрів методу

void someMethod(final String s){
    s = someOtherString; //compile error
}

Це просто означає, що значення finalопорного значення неможливо змінити. тобто дозволена лише одна ініціалізація. У цьому сценарії під час виконання, оскільки JRE знає, що значення неможливо змінити, він завантажує всі ці доопрацьовані значення (остаточних посилань) у кеш L1 . Тому що це не потрібно , щоб завантажити назад знову і знову з основної пам'яті . Інакше він завантажується в кеш-пам'ять L2 і робить час від часу завантаження з основної пам'яті. Тож це також поліпшення продуктивності.

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


0

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

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