Czy końcowe zmienne Java będą miały wartości domyślne?


81

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 initializedna 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 ...

Odpowiedzi:


62

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html , rozdział „Inicjowanie członków instancji”:

Kompilator Java kopiuje bloki inicjalizatora do każdego konstruktora.

To jest do powiedzenia:

{
    printX();
}

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

zachowuje się dokładnie tak, jak:

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

Jak zatem widać, po utworzeniu instancji ostatnie pole nie zostało ostatecznie przypisane , natomiast (z http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html # jls-8.3.1.2 ):

Pusta zmienna ostatniej instancji musi być ostatecznie przypisana na końcu każdego konstruktora klasy, w której jest zadeklarowana; w przeciwnym razie wystąpi błąd w czasie kompilacji.

Chociaż nie wydaje się, aby zostało to wyraźnie określone w dokumentach (przynajmniej nie byłem w stanie go znaleźć), końcowe pole musi tymczasowo przyjąć wartość domyślną przed końcem konstruktora, aby miało przewidywalną wartość, jeśli przeczytaj go przed zleceniem.

Wartości domyślne: http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5

W drugim fragmencie x jest inicjowane podczas tworzenia instancji, więc kompilator nie narzeka:

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

Pamiętaj również, że poniższe podejście nie działa. Używanie wartości domyślnej zmiennej końcowej jest dozwolone tylko za pomocą metody.

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
Warto zwrócić uwagę, gdzie w jednym z przykładów znajduje się niejawne (lub jawne) wywołanie super ().
Patrick

2
To nie wyjaśnia, dlaczego brak inicjalizacji pola końcowego powoduje błąd kompilacji.
justhalf

@ sp00m Dobra referencja - umieszczę to w banku.
Bohemian

2
@justhalf w odpowiedzi brakuje kluczowego punktu. Możesz uzyskać dostęp do finału w jego stanie domyślnym (za pomocą metody), ale kompilator będzie narzekał, jeśli nie zainicjujesz go przed zakończeniem procesu konstrukcji. Dlatego druga próba działa (faktycznie inicjalizuje x), ale nie pierwsza. Kompilator będzie również narzekał, jeśli spróbujesz uzyskać bezpośredni dostęp do pustego finału.
Luca

28

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ść xjest definitywnie przypisana na początku konstruktora, ponieważ zawiera przypisanie na końcu.


Właściwie mówi , że nie możesz uzyskać do niego dostępu przed przypisaniem : „Każda zmienna lokalna (§14.4) i każde puste pole końcowe (§4.12.4, §8.3.1.2) musi mieć ostatecznie przypisaną wartość, gdy nastąpi jakikolwiek dostęp do jej wartości "
l4mpi

1
powinno być "zdecydowanie przypisane", jednak ta reguła ma dziwne zachowanie pod względem konstruktora, zaktualizowałem odpowiedź
udalmik

Jeśli istnieje metoda kodu, która w zależności od pewnych złożonych warunków może odczytywać finalpole 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.
supercat

7

Jeśli nie zainicjujesz x, pojawi się błąd kompilacji, ponieważ xnigdy nie jest inicjowany.

Zadeklarowanie xjako 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 0drukujesz przed zainicjalizowaniem zmiennej, jest spowodowany zachowaniem zdefiniowanym w instrukcji (patrz: sekcja "Wartości domyślne"):

Wartości domyślne

Nie zawsze jest konieczne przypisywanie wartości, gdy deklarowane jest pole. Pola, które są zadeklarowane, ale nie zostały zainicjowane, zostaną ustawione na rozsądną wartość domyślną przez kompilator. Ogólnie rzecz biorąc, ta wartość domyślna będzie wynosić zero lub null, w zależności od typu danych. Jednak poleganie na takich wartościach domyślnych jest ogólnie uważane za zły styl programowania.

Poniższy wykres zawiera podsumowanie wartości domyślnych dla powyższych typów danych.

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

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ą.


2

Wszystkie nie-końcowe pola klasy są inicjowane do wartości domyślnej ( 0dla typów danych nummerycznych, falsedla typów logicznych i nullreferencyjnych, 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.


1

Powiem to w najprostszych słowach, jakie potrafię.

finalzmienne 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 staticinon-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 xprzyjmuje 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 jak x=7. Zobacz wywołanie init poniżej:

wprowadź opis obrazu tutaj


1

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

1
Ostatnia zmienna x nie jest statyczna w kodzie OP.
JamesB

Mógłbym równie łatwo zmodyfikować kod OP, aby zainicjować this.x i to samo by się stało. Nie ma znaczenia, czy jest statyczny, czy nie.
Michael D.

Sugerowałbym usunięcie tutaj statycznej zawartości, ponieważ wygląda na to, że nie przeczytałeś pytania OP.
JamesB

Czy to pomoże, jeśli zacznę bazować na kodzie OP? Jak powiedziałem, nie ma znaczenia, czy zmienna jest statyczna, czy nie. Chodzi mi o to, że zainicjowanie zmiennej dla siebie samej i uzyskanie wartości domyślnej oznacza, że ​​zmienna zostanie niejawnie zainicjowana przed jawną inicjalizacją.
Michael D.

1
nie kompiluje się, ponieważ próbujesz uzyskać (bezpośrednio) dostęp do ostatniej zmiennej przed jej zainicjalizowaniem, w linii 6.
Luca

1

Jeśli spróbuję go wykonać, otrzymuję błąd kompilatora, ponieważ: zmienna x mogła nie zostać zainicjowana na podstawie domyślnych wartości Java. Poniższe dane wyjściowe powinny być prawidłowe?

„Tutaj x jest 0”.

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.