Дуже цікава знахідка. Щоб зрозуміти це, нам потрібно скористатися специфікацією мови Java ( JLS ).
Причина в тому, що final
дозволяє лише одне завдання . Типовим значенням, однак, не є призначення . Фактично кожна така змінна ( змінна класу, змінна інстанція, компонент масиву) вказує на її значення за замовчуванням з початку, перед призначенням . Перше призначення потім змінює посилання.
Змінні класу та значення за замовчуванням
Погляньте на наступний приклад:
private static Object x;
public static void main(String[] args) {
System.out.println(x); // Prints 'null'
}
Ми не призначали явно значення x
, хоча воно вказує на null
це значення за замовчуванням. Порівняйте це з §4.12.5 :
Початкові значення змінних
Кожна змінна клас, змінна інстанція або компонент масиву ініціалізуються зі значенням за замовчуванням, коли воно створюється ( §15.9 , §15.10.2 )
Зауважте, що це стосується лише таких змінних, як у нашому прикладі. Він не містить місцевих змінних, див. Наступний приклад:
public static void main(String[] args) {
Object x;
System.out.println(x);
// Compile-time error:
// variable x might not have been initialized
}
З того ж пункту JLS:
Локальна змінна ( §14.4 , §14.14 ) має бути явно присвоєно значення , перш ніж вона використовується, або ініціалізації ( §14.4 ) або привласнення ( §15.26 ), таким чином , що можна перевірити , використовуючи правила для певного присвоювання ( § 16 (Визначене призначення) ).
Кінцеві змінні
Тепер ми розглянемо final
з §4.12.4 :
остаточні змінні
Змінна може бути оголошена остаточною . Остаточна змінна може бути тільки призначені один раз . Це помилка часу компіляції, якщо присвоєно остаточну змінну, якщо вона точно не призначена безпосередньо перед призначенням ( §16 (Визначення призначення) ).
Пояснення
Тепер повернемось до вашого прикладу, трохи зміненого:
public static void main(String[] args) {
System.out.println("After: " + X);
}
private static final long X = assign();
private static long assign() {
// Access the value before first assignment
System.out.println("Before: " + X);
return X + 1;
}
Він виводить
Before: 0
After: 1
Згадайте, що ми дізналися. Всередині методу assign
змінній X
ще не було призначено значення. Отже, воно вказує на його значення за замовчуванням, оскільки це змінна категорія, і згідно з JLS, ці змінні завжди одразу вказують на їх значення за замовчуванням (на відміну від локальних змінних). Після assign
методу змінній X
присвоюється значення, 1
і final
ми не можемо її більше змінити. Отже, наступне не працюватиме через final
:
private static long assign() {
// Assign X
X = 1;
// Second assign after method will crash
return X + 1;
}
Приклад в JLS
Завдяки @Andrew я знайшов абзац JLS, який стосується саме цього сценарію, він також демонструє це.
Але спочатку давайте подивимось
private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer
Чому це не дозволено, тоді як доступ від методу є? Погляньте на пункт 8.3.3, який говорить про те, коли доступ до полів обмежений, якщо поле ще не було ініціалізовано.
У ньому перелічено деякі правила, що стосуються змінних класу:
Для посилання простою назвою на змінну класу, f
оголошену в класі або інтерфейсі C
, це помилка часу компіляції, якщо :
Посилання з’являється або в ініціалізаторі змінної класу, C
або в статичному ініціалізаторі C
( §8.7 ); і
Посилання з'являється або в ініціалізаторі f
власного декларатора, або в точці зліва від f
декларатора; і
Посилання не на лівій частині виразу присвоєння ( §15.26 ); і
Найпотаємніший клас або інтерфейс, що додає посилання C
.
Це просто, X = X + 1
це потрапляє під ці правила, метод не доступний. Вони навіть перераховують цей сценарій і наводять приклад:
Доступ методами не перевіряється таким чином:
class Z {
static int peek() { return j; }
static int i = peek();
static int j = 1;
}
class Test {
public static void main(String[] args) {
System.out.println(Z.i);
}
}
виробляє вихід:
0
тому що ініціалізатор змінної для i
використання методу класу peek для доступу до значення змінної j
раніше j
був ініціалізований ініціалізатором змінної, і в цей момент вона все ще має своє значення за замовчуванням ( §4.12.5 ).
X
член схожий на посилання на члена підкласу до закінчення конструктора суперкласу, це ваша проблема, а не визначенняfinal
.