W zakresie metody lub klasy kompiluje się poniższy wiersz (z ostrzeżeniem):
int x = x = 1;
W zakresie klasy, gdzie zmienne pobierają wartości domyślne , następujący komunikat powoduje błąd „niezdefiniowane odwołanie”:
int x = x + 1;
Czy to nie pierwsza x = x = 1
powinna zakończyć się tym samym błędem „niezdefiniowane odniesienie”? A może druga linia int x = x + 1
powinna się skompilować? Czy jest coś, czego mi brakuje?
java
compiler-construction
Marcin
źródło
źródło
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.static int x = x + 1
nie działa w Javie.int a = this.a + 1;
iwint b = 1; int a = b + 1;
zakresie klasy (oba są w porządku w Javie) zawodzą, prawdopodobnie z powodu §17.4.5.2 - „Inicjator zmiennej dla pola instancji nie może odwoływać się do tworzonej instancji”. Nie wiem, czy gdzieś jest to wyraźnie dozwolone, ale statyczność nie ma takiego ograniczenia. W Javie zasady są inne istatic int x = x + 1
int x = x + 1
Odpowiedzi:
tl; dr
Na polach ,
int b = b + 1
jest nielegalne, ponieważb
jest to niezgodne z prawem do przodu odniesieniab
. Możesz to naprawić, piszącint 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:
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:
m
zadeklarowanego lub dziedziczonego przez typ klasy C (§ 8.1.6) to cała treść C, w tym wszelkie zagnieżdżone deklaracje typu.W §8.3.2.3 czytamy:
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
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): odniesieniea
jest przypisywane, więc legalne jest odwoływanie się do niegoa
przeda
pełną deklaracją.int b = this.b + 1
również kompiluje się, ponieważ narusza (c): odwołaniethis.b
nie jest prostą nazwą (jest kwalifikowanethis.
). 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 nie
int 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:
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:
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 dod
jest rozstrzygany na zmienną lokalną dobrze, ale ponieważd
nie został przypisany przed uzyskaniemd
dostępu, kompilator zgłasza błąd. Wint c = c = 1
,c = 1
dzieje się najpierw, co przypisujec
, a następniec
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 polaint b = (b = 1) + b
), ponieważd
jest ostatecznie przypisana do czasu osiągnięcia finałud
.źródło
int b = b + 1
b znajduje się po prawej stronie (nie po lewej) zadania, więc naruszyłoby to ...int x = x = 1
, w którym w przypadku gdy nic z tego nie miałoby zastosowania.jest równa
będąc w
najpierw musimy obliczyć,
x+1
ale wartość x nie jest znana, więc pojawia się błąd (kompilator wie, że wartość x nie jest znana)źródło
int x = x = 1;
odpowiadaint x = (x = 1)
, niex = 1; x = x;
. Nie powinieneś otrzymywać ostrzeżenia kompilatora, aby to zrobić.int x = x = 1;
ekwiwalent intx = (x = 1)
powodu prawe asocjatywności=
operatoraint x = (x = 1)
jest równoważneint x; x = 1; x = x;
(deklaracja zmiennej, ocena inicjalizatora pola, przypisanie zmiennej do wyniku tej oceny), stąd ostrzeżenieJest to mniej więcej równoważne z:
Po pierwsze,
int <var> = <expression>;
jest zawsze równoważneW tym przypadku twoje wyrażenie jest
x = 1
, które jest również stwierdzeniem.x = 1
jest prawidłową instrukcją, ponieważ zmiennax
została już zadeklarowana. Jest to również wyrażenie o wartości 1, do którego jest następnie przypisywanax
ponownie.źródło
0
wartość domyślną dla ints, więc spodziewałbym się, że wynikiem będzie 1, a nieundefined reference
.x + 1
nie ma zdefiniowanej wartości, ponieważ niex
jest zainicjowany.x
jest zdefiniowana jako zmienna składowa („w zakresie klasy”).W Javie lub jakimkolwiek innym nowoczesnym języku przypisanie pochodzi z prawej strony.
Załóżmy, że masz dwie zmienne x i y,
Ta instrukcja jest prawidłowa i tak kompilator je dzieli.
Ale w twoim przypadku
Kompilator dał wyjątek, ponieważ dzieli się w ten sposób.
źródło
int x = x = 1;
nie jest równe:javap znowu nam pomaga, oto instrukcja JVM wygenerowana dla tego kodu:
bardziej jak:
Nie ma powodu, aby zgłaszać niezdefiniowany błąd odwołania. Obecnie używa się zmiennej przed jej inicjalizacją, więc ten kod jest w pełni zgodny ze specyfikacją. W rzeczywistości nie ma w ogóle użycia zmiennej , tylko przypisania. A kompilator JIT pójdzie jeszcze dalej, wyeliminuje takie konstrukcje. Mówiąc szczerze, nie rozumiem, jak ten kod jest powiązany ze specyfikacją JLS dotyczącą inicjalizacji i użycia zmiennych. Bez użycia bez problemów. ;)
Popraw, jeśli się mylę. Nie mogę zrozumieć, dlaczego inne odpowiedzi, które odnoszą się do wielu akapitów JLS, mają tak wiele plusów. Te akapity nie mają nic wspólnego z tym przypadkiem. Tylko dwa zlecenia seryjne i nic więcej.
Jeśli napiszemy:
jest równe:
Najbardziej prawe wyrażenie jest po prostu przypisywane do zmiennych jedna po drugiej, bez żadnej rekursji. Możemy manipulować zmiennymi w dowolny sposób:
źródło
W
int x = x + 1;
dodaniu 1 do x, więc to, co jest wartościąx
, to nie jest jeszcze utworzony.Ale program
int x=x=1;
skompiluje się bez błędu, ponieważ przypisujesz 1 dox
.źródło
Twój pierwszy fragment kodu zawiera drugi
=
zamiast plusa. Spowoduje to skompilowanie się w dowolnym miejscu, podczas gdy drugi fragment kodu nie zostanie skompilowany w żadnym miejscu.źródło
W drugim fragmencie kodu x jest używane przed deklaracją, podczas gdy w pierwszym fragmencie kodu jest po prostu przypisane dwukrotnie, co nie ma sensu, ale jest poprawne.
źródło
Rozbijmy to krok po kroku, prawidłowa asocjacja
x = 1
, przypisz 1 do zmiennej xint x = x
, przypisz sobie, czym jest x, jako int. Ponieważ x był wcześniej przypisany jako 1, zachowuje 1, aczkolwiek w sposób zbędny.To dobrze się komponuje.
x + 1
dodaj jeden do zmiennej x. Jednak niezdefiniowanie x spowoduje błąd kompilacji.int x = x + 1
, więc ta linia błędów kompilacji, ponieważ prawa część równych nie będzie kompilować dodawania jednego do nieprzypisanej zmiennejźródło
=
operatory, więc jest taki sam jakint x = (x = 1);
.Drugi
int x=x=1
jest kompilowany, ponieważ przypisujesz wartość do x, ale w innym przypadkuint x=x+1
tutaj zmienna x nie jest inicjowana. Pamiętaj, że zmienne lokalne java nie są inicjowane do wartości domyślnej. Uwaga Jeśli to (int x=x+1
) jest również w zakresie klasy, to również spowoduje błąd kompilacji, ponieważ zmienna nie jest tworzona.źródło
kompiluje się pomyślnie w programie Visual Studio 2008 z ostrzeżeniem
źródło
c
zamiast tagu widziałem tag,java
ale najwyraźniej było to drugie pytanie.bool y;
iy==true
zwróci false.void main() { int x = x + 1; printf("%d ", x); }
w Visual Studio 2008, w Debug otrzymuję wyjątek,Run-Time Check Failure #3 - The variable 'x' is being used without being initialized.
aw Release otrzymuję numer1896199921
wydrukowany w konsoli.static
pola (zmienna statyczna na poziomie klasy) obowiązują te same zasady. Na przykład pole zadeklarowane jakopublic static int x = x + 1;
kompiluje się bez ostrzeżenia w programie Visual C #. Prawdopodobnie to samo w Javie?x nie jest inicjalizowany w
x = x + 1
;.Język programowania Java jest typowany statycznie, co oznacza, że wszystkie zmienne muszą być najpierw zadeklarowane, zanim będą mogły być używane.
Zobacz prymitywne typy danych
źródło
Wiersz kodu nie kompiluje się z ostrzeżeniem, ponieważ kod faktycznie działa. Po uruchomieniu kodu
int x = x = 1
Java najpierw tworzy zmiennąx
zgodnie z definicją. Następnie uruchamia kod przypisania (x = 1
). Ponieważx
jest już zdefiniowane, system nie ma błędów, ustawiając wartośćx
na 1. Zwraca to wartość 1, ponieważ jest to teraz wartośćx
. W związku z tymx
jest teraz ostatecznie ustawiony na 1.Java zasadniczo wykonuje kod tak, jakby to było to:
Jednak w drugim kawałkiem kodu,
int x = x + 1
The+ 1
stwierdzenie wymagax
, aby zdefiniować, który przez to nie jest. Ponieważ instrukcje przypisania zawsze oznaczają, że kod po prawej stronie=
jest uruchamiany jako pierwszy, kod zakończy się niepowodzeniem, ponieważx
jest niezdefiniowany. Java uruchomiłaby kod w ten sposób:źródło
Zgodny czytaj oświadczenia od prawej do lewej i postanowiliśmy zrobić coś przeciwnego. Dlatego na początku to denerwowało. Niech to przyzwyczajenie do czytania instrukcji (kodu) od prawej do lewej, nie będziesz miał takiego problemu.
źródło