Mam taki program:
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();
}
}
Jeśli spróbuję go wykonać, otrzymuję błąd kompilatora, ponieważ: variable x might not have been initialized
na podstawie domyślnych wartości java powinienem uzyskać prawidłowe dane wyjściowe?
"Here x is 0".
Czy końcowe zmienne będą miały wartości domyślne?
jeśli zmienię swój kod w ten sposób,
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();
}
}
Otrzymuję dane wyjściowe jako:
Here x is 0
Here x is 7
const called
Czy ktoś może wyjaśnić to zachowanie ...
JLS jest powiedzenie , że trzeba przypisać wartość domyślną do pustej końcowego zmiennej instancji w konstruktorze (lub w bloku inicjalizacji , która jest całkiem takie same). Dlatego w pierwszym przypadku pojawia się błąd. Nie oznacza to jednak, że wcześniej nie można było uzyskać do niego dostępu w konstruktorze. Wygląda trochę dziwnie, ale możesz uzyskać do niego dostęp przed przypisaniem i zobaczyć domyślną wartość int - 0.
UPD. Jak wspomniano w @ I4mpi, JLS definiuje regułę, że każda wartość powinna być zdecydowanie przypisana przed każdym dostępem:
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.
Jednak ma też ciekawą regułę dotyczącą konstruktorów i pól:
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.
Więc w drugim przypadku wartość
x
jest definitywnie przypisana na początku konstruktora, ponieważ zawiera przypisanie na końcu.źródło
final
pole lub nie , i jeśli ten kod może być uruchamiany zarówno przed, jak i po zapisaniu pola, kompilator w ogólnym przypadku nie miałby możliwości wiedząc, czy kiedykolwiek rzeczywiście przeczyta pole, zanim zostanie napisane.Jeśli nie zainicjujesz
x
, pojawi się błąd kompilacji, ponieważx
nigdy nie jest inicjowany.Zadeklarowanie
x
jako ostateczne oznacza, że można go zainicjować tylko w konstruktorze lub w bloku inicjalizatora (ponieważ ten blok zostanie skopiowany przez kompilator do każdego konstruktora).Powód, dla którego
0
drukujesz przed zainicjalizowaniem zmiennej, jest spowodowany zachowaniem zdefiniowanym w instrukcji (patrz: sekcja "Wartości domyślne"):Wartości domyślne
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
źródło
Pierwszym błędem jest to, że kompilator narzeka, że masz ostatnie pole, ale nie ma kodu do jego zainicjowania - dość proste.
W drugim przykładzie masz kod, który przypisuje mu wartość, ale kolejność wykonywania oznacza, że odwołujesz się do pola zarówno przed, jak i po przypisaniu go.
Wstępnie przypisana wartość dowolnego pola jest wartością domyślną.
źródło
Wszystkie nie-końcowe pola klasy są inicjowane do wartości domyślnej (
0
dla typów danych nummerycznych,false
dla typów logicznych inull
referencyjnych, czasami nazywane obiektami złożonymi). Te pola są inicjowane przed wykonaniem konstruktora (lub bloku inicjalizacji wystąpienia) niezależnie od tego, czy pola zostały zadeklarowane przed konstruktorem, czy po nim.Końcowe pola klasy nie mają wartości domyślnej i muszą być jawnie zainicjowane tylko raz, zanim konstruktor klasy zakończy swoje zadanie.
Zmienne lokalne wewnątrz bloku wykonywania (na przykład metoda) nie mają wartości domyślnej. Te pola muszą być jawnie zainicjowane przed ich pierwszym użyciem i nie ma znaczenia, czy zmienna lokalna jest oznaczona jako ostateczna.
źródło
Powiem to w najprostszych słowach, jakie potrafię.
final
zmienne muszą zostać zainicjowane, jest to wymagane przez specyfikację języka. To powiedziawszy, proszę zauważyć, że nie jest konieczne jego inicjowanie w momencie zgłoszenia.Wymagane jest zainicjowanie tego przed zainicjowaniem obiektu.
Możemy użyć bloków inicjalizujących, aby zainicjować końcowe zmienne. Teraz bloki inicjalizatora są dwóch typów
static
inon-static
Użyty blok to niestatyczny blok inicjujący. Tak więc, kiedy tworzysz obiekt, Runtime wywoła konstruktor, a ten z kolei wywoła konstruktor klasy nadrzędnej.
Następnie wywoła wszystkie inicjatory (w twoim przypadku inicjator niestatyczny).
W twoim pytaniu, przypadek 1 : Nawet po zakończeniu bloku inicjalizatora ostateczna zmienna pozostaje niezainicjalizowana, co jest błędem, który kompilator wykryje.
W przypadku 2 : inicjalizator zainicjuje ostateczną zmienną, stąd kompilator wie, że przed zainicjalizowaniem obiektu finalna jest już zainicjalizowana. Dlatego nie będzie narzekać.
Teraz pytanie brzmi, dlaczego
x
przyjmuje zero. Powodem jest to, że kompilator już wie, że nie ma błędu, a więc po wywołaniu metody init wszystkie finały zostaną zainicjowane do wartości domyślnych i ustawiona flaga, którą mogą zmienić po rzeczywistej instrukcji przypisania, podobnie jakx=7
. Zobacz wywołanie init poniżej:źródło
O ile mi wiadomo, kompilator zawsze inicjalizuje zmienne klasy do wartości domyślnych (nawet zmiennych końcowych). Na przykład, jeśli miałbyś zainicjować int do siebie samego, int zostałby ustawiony na wartość domyślną 0. Zobacz poniżej:
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(); } }
Powyższe spowodowałoby wydrukowanie:
Here x is 0 Here x is 0 const called
źródło
Nie. Nie widzisz tych danych wyjściowych, ponieważ w pierwszej kolejności otrzymujesz błąd kompilacji. Zmienne końcowe mają wartość domyślną, ale specyfikacja języka Java (JLS) wymaga zainicjowania ich na końcu konstruktora (LE: w tym miejscu włączam bloki inicjalizacyjne), w przeciwnym razie wystąpi błąd w czasie kompilacji, który zapobiegnie kompilacji i wykonaniu twojego kodu.
Twój drugi przykład jest zgodny z wymaganiem, dlatego (1) Twój kod się kompiluje i (2) uzyskujesz oczekiwane zachowanie.
W przyszłości spróbuj zapoznać się z JLS. Nie ma lepszego źródła informacji o języku Java.
źródło