Чи матимуть фінальні змінні Java значення за замовчуванням?


81

У мене така програма:

class Test {

    final int x;

    {
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }

}

Якщо я намагаюся його виконати, я отримую помилку компілятора як: variable x might not have been initializedна основі значень Java за замовчуванням я повинен отримати нижченаведений вивід правильно ??

"Here x is 0".

Чи матимуть кінцеві змінні значення за замовчуванням?

якщо я зміню свій код так,

class Test {

    final int x;

    {
        printX();
        x = 7;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }

}

Я отримую результат як:

Here x is 0                                                                                      
Here x is 7                                                                                     
const called

Хто-небудь може пояснити цю поведінку ..

Відповіді:


62

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html , глава "Ініціалізація членів екземпляру":

Компілятор Java копіює блоки ініціалізатора в кожен конструктор.

Тобто:

{
    printX();
}

Test() {
    System.out.println("const called");
}

поводиться точно так:

Test() {
    printX();
    System.out.println("const called");
}

Як ви можете бачити, після створення екземпляра останнє поле точно не було призначено , тоді як (з http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html # jls-8.3.1.2 ):

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

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

Значення за замовчуванням: http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5

У вашому другому фрагменті x ініціалізується при створенні екземпляра, тому компілятор не скаржиться:

Test() {
    printX();
    x = 7;
    printX();
    System.out.println("const called");
}

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

Test() {
    System.out.println("Here x is " + x); // Compile time error : variable 'x' might not be initialized
    x = 7;
    System.out.println("Here x is " + x);
    System.out.println("const called");
}

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

2
Це не відповідає, чому не ініціалізація остаточного поля викликає помилку компіляції.
justhalf

@ sp00m Хороша довідка - я покладу це в банк.
Чеська

2
@justhalf у відповіді відсутній вирішальний момент. Ви можете отримати доступ до фіналу у стані за замовчуванням (за допомогою методу), але компілятор скаржиться, якщо ви не ініціалізуєте його до кінця процесу побудови. Ось чому друга спроба працює (вона фактично ініціалізує x), але не перша. Компілятор також скаржиться, якщо ви спробуєте отримати безпосередній доступ до пустого фіналу.
Лука

28

JLS це говорять , що ви повинні привласнити значення за замовчуванням порожній остаточної змінної примірника в конструкторі (або в блоці ініціалізації , який досить те ж саме). Ось чому ви отримуєте помилку в першому випадку. Однак це не говорить, що ви не можете отримати до нього доступ у конструкторі раніше. Виглядає трохи дивно, але ви можете отримати доступ до нього перед призначенням і побачити значення за замовчуванням для int - 0.

UPD. Як згадував @ I4mpi, JLS визначає правило, згідно з яким кожне значення має бути визначене перед будь-яким доступом:

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.

Однак у нього також є цікаве правило стосовно конструкторів та полів:

If C has at least one instance initializer or instance variable initializer then V is [un]assigned after an explicit or implicit superclass constructor invocation if V is [un]assigned after the rightmost instance initializer or instance variable initializer of C.

Тому в другому випадку значення xє виразно присвоєної на початку конструктора, так як він містить завдання на кінці.


На насправді, це робить , що ви не можете отримати доступ до нього , перш ніж присвоювання : «Кожна локальна змінна (§14.4) і кожен порожній останнє поле (§4.12.4, §8.3.1.2) повинні бути , безумовно , присвоєне значення , коли який - або доступ до його вартості відбувається "
l4mpi

1
його слід "визначити", проте це правило має дивну поведінку з точки зору конструктора, я оновив відповідь
udalmik

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

7

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

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

Причиною того, що вас 0роздруковують перед ініціалізацією змінної, є поведінка, визначена в посібнику (див .: розділ "Значення за замовчуванням"):

Значення за замовчуванням

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

Наступна діаграма узагальнює значення за замовчуванням для вищезазначених типів даних.

Data Type   Default Value (for fields)
--------------------------------------
byte        0
short       0
int         0
long        0L
float       0.0f
double      0.0d
char        '\u0000'
String (or any object)      null
boolean     false

4

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

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

Попередньо призначене значення будь-якого поля є значенням за замовчуванням.


2

Всі 0нефінальні поля класу ініціалізуються до значення за замовчуванням ( для числових типів даних, falseдля логічного таnull для посилальних типів, які іноді називають складними об'єктами). Ці поля ініціалізуються до того, як конструктор (або блок ініціалізації екземпляра) виконується незалежно від того, були чи не оголошені поля до чи після конструктора.

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

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


1

Дозвольте сказати це найпростішими словами, які я можу.

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

Потрібно ініціалізувати це перед ініціалізацією об’єкта.

Ми можемо використовувати блоки ініціалізації для ініціалізації кінцевих змінних. Тепер блоки ініціалізатора бувають двох типів staticіnon-static

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

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

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

У випадку 2 : Ініціалізатор ініціалізує кінцеву змінну, отже, компілятор знає, що перед ініціалізацією об’єкта фінал вже ініціалізований. Отже, він не буде скаржитися.

Тепер питання в тому, чому xприймає нуль. Причина тут полягає в тому, що компілятор вже знає, що помилки немає, і тому при виклику методу init всі фінали будуть ініціалізовані за замовчуванням, і встановлений прапор, який вони можуть змінити за фактичним оператором призначення, подібним до x=7. Дивіться виклик ініціювання нижче:

введіть тут опис зображення


1

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

class Test {
    final int x;

    {
        printX();
        x = this.x;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }
}

Вище буде надруковано наступне:

Here x is 0
Here x is 0
const called

1
Кінцева змінна x не є статичною в коді OP.
JamesB

Я міг би так само легко змінити код OP для ініціалізації до this.x, і те ж саме відбулося б. Неважливо, статичний він чи ні.
Michael D.

Я б запропонував вам видалити тут статичний вміст, оскільки, схоже, ви не читали питання OP.
JamesB

Чи допоможе це, якщо я тоді буду базуватися на коді ОП? Як я вже сказав, не має значення статична змінна чи ні. Моя думка полягає в тому, що ініціалізація змінної для себе і отримання значення за замовчуванням означає, що змінна дійсно неявно ініціалізується перед тим, як її явно ініціалізувати.
Michael D.

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

1

Якщо я спробую її виконати, я отримую помилку компілятора, оскільки: змінна x, можливо, не була ініціалізована на основі значень за замовчуванням Java, я повинен отримати нижченаведений вивід правильно ??

"Тут x дорівнює 0".

Ні. Ви не бачите цього результату, оскільки спочатку ви отримуєте помилку під час компіляції. Кінцеві змінні отримують значення за замовчуванням, але специфікація мови Java (JLS) вимагає ініціалізації їх до кінця конструктора (LE: я включаю блоки ініціалізації сюди), інакше ви отримаєте помилку під час компіляції, яка запобіжить компіляції та виконанню вашого коду.

Ваш другий приклад поважає вимогу, ось чому (1) ваш код компілюється і (2) ви отримуєте очікувану поведінку.

Надалі спробуйте ознайомитись із JLS. Немає кращого джерела інформації про мову Java.

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