Pytanie 1:
Dlaczego poniższy kod kompiluje się bez instrukcji return?
public int a() {
while(true);
}
Uwaga: jeśli dodam zwrot po pewnym czasie, otrzymam Unreachable Code Error
.
Pytanie 2:
Z drugiej strony, dlaczego kompiluje następujący kod,
public int a() {
while(0 == 0);
}
nawet jeśli poniższe nie.
public int a(int b) {
while(b == b);
}
java
syntax
while-loop
compilation
return
Willi Mentzel
źródło
źródło
Odpowiedzi:
Jest to objęte JLS§8.4.7 :
Ponieważ kompilator wie, że pętla nigdy się nie kończy (
true
oczywiście zawsze jest to prawda), wie, że funkcja nie może „powrócić normalnie” (zrzucić koniec swojego ciała), a zatem jest w porządku, że jej nie mareturn
.W takim
0 == 0
przypadku kompilator wie, że pętla nigdy się nie zakończy (0 == 0
zawsze będzie to prawdą). Ale nie wie o tymb == b
.Dlaczego nie?
Kompilator rozumie wyrażenia stałe (§15.28) . Cytując § 15.2 - Formy wyrażeń (ponieważ dziwnie tego zdania nie ma w §15.28) :
W twoim
b == b
przykładzie, ponieważ w grę wchodzi zmienna, nie jest to stałe wyrażenie i nie jest określone, aby miało być określone w czasie kompilacji. Widzimy , że zawsze będzie to prawdą w tym przypadku (chociaż gdybyb
byłodouble
, jak wskazał QBrute , moglibyśmy się łatwo oszukaćDouble.NaN
, co nie==
jest samo w sobie ), ale JLS określa tylko, że wyrażenia stałe są określane w czasie kompilacji , nie pozwala kompilatorowi próbować oceniać wyrażeń niestałych. bayou.io podniósł dobrą rację, dlaczego nie: Jeśli zaczniesz iść drogą próbowania określenia wyrażeń obejmujących zmienne w czasie kompilacji, gdzie się zatrzymujesz?b == b
jest oczywiste (er, dlaNaN
wartości), ale co z tyma + b == b + a
? Czy(a + b) * 2 == a * 2 + b * 2
? Rysowanie linii na stałych ma sens.Ponieważ więc nie „określa” wyrażenia, kompilator nie wie, że pętla nigdy się nie zakończy, więc uważa, że metoda może powrócić normalnie - czego nie wolno, ponieważ jest wymagana
return
. Więc narzeka na brakreturn
.źródło
Interesujące może być pomyślenie o typie zwracanej metody nie jako obietnicy zwrócenia wartości określonego typu, ale jako obietnicy, że nie zwróci wartości, która nie jest określonego typu. Zatem jeśli nigdy niczego nie zwrócisz, nie złamiesz obietnicy, a zatem którykolwiek z poniższych elementów jest zgodny z prawem:
Zapętlanie na zawsze:
Recursing na zawsze:
Zgłaszając wyjątek:
(Uważam, że rekursja jest przyjemna do myślenia: kompilator uważa, że metoda zwróci wartość typu
X
(cokolwiek to jest), ale to nieprawda, ponieważ nie ma kodu, który miałby pojęcie, jak stworzyć lub zdobyćX
.)źródło
Patrząc na kod bajtu, jeśli zwracany kod nie odpowiada definicji, pojawi się błąd kompilacji.
Przykład:
for(;;)
pokaże kody bajtowe:Zwróć uwagę na brak kodu zwrotnego
To nigdy nie trafia w zwrot, a zatem nie zwraca niewłaściwego typu.
Dla porównania metoda taka jak:
Zwróci następujące kody bajtowe:
Zwróć uwagę na „areturn”, co oznacza „zwróć referencję”
Teraz, jeśli wykonamy następujące czynności:
Zwróci następujące kody bajtowe:
Teraz widzimy, że typ w definicji nie pasuje do zwracanego typu ireturn, co oznacza return int.
Tak naprawdę sprowadza się to do tego, że jeśli metoda ma ścieżkę zwrotną, ścieżka ta musi pasować do typu zwracanego. Są jednak przypadki w kodzie bajtowym, w których nie jest generowana żadna ścieżka zwrotna, a zatem nie dochodzi do złamania reguły.
źródło