tl; dr
Na polach , int b = b + 1
jest nielegalne, ponieważ b
jest to niezgodne z prawem do przodu odniesienia b
. Możesz to naprawić, pisząc int b = this.b + 1
, który kompiluje się bez skarg.
Dla zmiennych lokalnych , int d = d + 1
jest nielegalne, ponieważ d
nie jest zainicjowany przed użyciem. Nie dotyczy to pól, które są zawsze inicjowane domyślnie.
Możesz zobaczyć różnicę, próbując skompilować
int x = (x = 1) + x;
jako deklaracja pola i jako deklaracja zmiennej lokalnej. Pierwsza zawiedzie, ale druga odniesie sukces z powodu różnicy semantyki.
Wprowadzenie
Po pierwsze, reguły dla inicjatorów zmiennych lokalnych i pól są bardzo różne. Tak więc ta odpowiedź będzie dotyczyła zasad w dwóch częściach.
Będziemy używać tego programu testowego przez cały czas:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
Deklaracja b
jest nieprawidłowa i kończy się illegal forward reference
błędem.
Deklaracja d
jest nieprawidłowa i kończy się variable d might not have been initialized
błędem.
Fakt, że te błędy są różne, powinien wskazywać, że przyczyny błędów są również różne.
Pola
Inicjatory pól w Javie są regulowane przez JLS §8.3.2 , Inicjalizacja pól.
Zakres pola jest zdefiniowana w JLS §6.3 , Zakres deklaracji.
Odpowiednie zasady to:
- Zakres deklaracji elementu członkowskiego
m
zadeklarowanego lub dziedziczonego przez typ klasy C (§ 8.1.6) to cała treść C, w tym wszelkie zagnieżdżone deklaracje typu.
- Wyrażenia inicjujące dla zmiennych instancji mogą używać prostej nazwy dowolnej zmiennej statycznej zadeklarowanej lub dziedziczonej przez klasę, nawet takiej, której deklaracja pojawia się później tekstowo.
- Użycie zmiennych instancji, których deklaracje pojawiają się tekstowo po użyciu, jest czasami ograniczone, nawet jeśli te zmienne instancji znajdują się w zakresie. Szczegółowe zasady rządzące odniesieniem do zmiennych instancji znajdują się w §8.3.2.3.
W §8.3.2.3 czytamy:
Deklaracja elementu członkowskiego musi pojawić się tekstowo, zanim zostanie użyta, tylko jeśli element członkowski jest instancją (odpowiednio statyczną) polem klasy lub interfejsu C i spełnione są wszystkie poniższe warunki:
- Użycie występuje w inicjalizatorze zmiennej instancji (odpowiednio statycznej) w języku C lub w inicjatorze wystąpienia (odpowiednio statycznym) w języku C.
- Użycie nie znajduje się po lewej stronie zadania.
- Użycie odbywa się za pomocą prostej nazwy.
- C to najbardziej wewnętrzna klasa lub interfejs obejmujący użycie.
W rzeczywistości możesz odwoływać się do pól, zanim zostały zadeklarowane, z wyjątkiem niektórych przypadków. Te ograniczenia mają na celu zapobieganie kodowaniu, takim jak
int j = i;
int i = j;
z kompilacji. Specyfikacja języka Java mówi, że „powyższe ograniczenia mają na celu wychwycenie w czasie kompilacji cyklicznych lub w inny sposób nieprawidłowych inicjalizacji”.
Do czego właściwie sprowadzają się te zasady?
Krótko mówiąc, reguły mówią po prostu, że musisz zadeklarować pole przed odwołaniem do tego pola, jeśli (a) odwołanie znajduje się w inicjatorze, (b) odwołanie nie jest przypisane, (c) odwołanie jest prosta nazwa (bez kwalifikatorów takich jak this.
) i (d) nie ma do niej dostępu z poziomu klasy wewnętrznej. Zatem odniesienie w przód, które spełnia wszystkie cztery warunki, jest niedozwolone, ale odniesienie w przód, które nie powiedzie się w co najmniej jednym warunku, jest prawidłowe.
int a = a = 1;
kompiluje się, ponieważ narusza (b): odniesienie a
jest przypisywane, więc legalne jest odwoływanie się do niego a
przed a
pełną deklaracją.
int b = this.b + 1
również kompiluje się, ponieważ narusza (c): odwołanie this.b
nie jest prostą nazwą (jest kwalifikowane this.
). Ta dziwna konstrukcja jest nadal doskonale zdefiniowana, ponieważ this.b
ma wartość zero.
Zasadniczo więc ograniczenia dotyczące odwołań do pól w inicjatorach uniemożliwiają int a = a + 1
pomyślną kompilację.
Zwróć uwagę, że deklaracja pola nieint b = (b = 1) + b
zostanie skompilowana, ponieważ wersja ostateczna nadal jest niedozwolonym odwołaniem do przodu.b
Zmienne lokalne
Lokalne deklaracje zmiennych są regulowane przez JLS §14.4 , Deklaracje deklaracji zmiennych lokalnych.
Zakres zmiennej lokalnej jest zdefiniowana w JLS §6.3 , Zakres deklaracji:
- Zakres deklaracji zmiennej lokalnej w bloku (punkt 14.4) to reszta bloku, w którym deklaracja się pojawia, zaczynając od własnego inicjatora i włączając wszelkie dalsze deklaratory po prawej stronie w instrukcji deklaracji lokalnej zmiennej.
Zwróć uwagę, że inicjatory znajdują się w zakresie deklarowanej zmiennej. Więc dlaczego się nie int d = d + 1;
kompiluje?
Powodem jest reguła Javy dotycząca określonego przypisania ( JLS §16 ). Określone przypisanie zasadniczo mówi, że każdy dostęp do zmiennej lokalnej musi mieć poprzedzające przypisanie do tej zmiennej, a kompilator Java sprawdza pętle i gałęzie, aby upewnić się, że przypisanie zawsze występuje przed jakimkolwiek użyciem (dlatego przypisanie określone ma całą sekcję specyfikacji poświęconą do niego). Podstawowa zasada to:
- Dla każdego dostępu do zmiennej lokalnej lub pustego pola końcowego
x
, x
musi być ostatecznie przypisany przed dostępem lub wystąpi błąd w czasie kompilacji.
W int d = d + 1;
programie dostęp do d
jest rozstrzygany na zmienną lokalną dobrze, ale ponieważ d
nie został przypisany przed uzyskaniem d
dostępu, kompilator zgłasza błąd. W int c = c = 1
, c = 1
dzieje się najpierw, co przypisuje c
, a następnie c
jest inicjowane w wyniku tego przypisania (czyli 1).
Zwróć uwagę, że ze względu na określone reguły przypisywania deklaracja zmiennej lokalnej int d = (d = 1) + d;
zostanie pomyślnie skompilowana (w przeciwieństwie do deklaracji pola int b = (b = 1) + b
), ponieważ d
jest ostatecznie przypisana do czasu osiągnięcia finału d
.
static
do zmiennej zakresu klasy, jak wstatic int x = x + 1;
, czy wystąpi ten sam błąd? Ponieważ w C # ma znaczenie, czy jest statyczny, czy niestatyczny.