Specyfika tego przypadku Java (które prawdopodobnie są bardzo podobne do przypadku C #) dotyczy sposobu, w jaki kompilator Java określa, czy metoda jest w stanie zwrócić.
W szczególności reguły są takie, że metoda z typem zwracanym nie może być w stanie zakończyć się normalnie i zamiast tego musi zawsze zakończyć się nagle (tutaj nagle, wskazując za pomocą instrukcji return lub wyjątku) zgodnie z JLS 8.4.7 .
Jeśli metoda ma zadeklarowany typ zwracany, wówczas błąd kompilacji występuje, jeśli treść metody może się normalnie zakończyć. Innymi słowy, metoda z typem zwracanym musi zwrócić tylko za pomocą instrukcji return, która zapewnia zwrot wartości; nie wolno „zrzucić końca jego ciała” .
Kompilator sprawdza, czy możliwe jest normalne zakończenie na podstawie reguł określonych w JLS 14.21 Nieosiągalne instrukcje ponieważ definiuje również reguły normalnego zakończenia.
W szczególności zasady dotyczące instrukcji nieosiągalnych stanowią szczególny przypadek tylko dla pętli, które mają zdefiniowane true
stałe wyrażenie:
Instrukcja while może zakończyć się normalnie, jeśli jest spełniony przynajmniej jeden z następujących warunków:
Instrukcja while jest osiągalna, a wyrażenie warunkowe nie jest wyrażeniem stałym (§ 15.28) o wartości true.
Istnieje osiągalna instrukcja break, która wychodzi z instrukcji while.
Jeśli więc while
instrukcja może się normalnie wypełnić , to konieczne jest podanie poniższej instrukcji return, ponieważ kod jest uznawany za osiągalny, a każda while
pętla bez osiągalnej instrukcji break lub stałejtrue
wyrażenia jest uważana za zdolną do ukończenia normalnie.
Reguły te oznaczają, że twoja while
instrukcja ze stałym prawdziwym wyrażeniem i bez a nigdy niebreak
jest uważana za zakończoną normalnie , więc żaden kod poniżej niej nigdy nie jest uważany za osiągalny . Koniec metody znajduje się poniżej pętli, a ponieważ wszystko poniżej pętli jest nieosiągalne, tak samo jest koniec metody, a zatem metoda nie może zakończyć się normalnie (tego szuka kompilator).
if
instrukcje, z drugiej strony, nie mają specjalnego wyłączenia dotyczącego stałych wyrażeń, które są zapewnione pętlom.
Porównać:
// I have a compiler error!
public boolean testReturn()
{
final boolean condition = true;
if (condition) return true;
}
Z:
// I compile just fine!
public boolean testReturn()
{
final boolean condition = true;
while (condition)
{
return true;
}
}
Powód tego rozróżnienia jest dość interesujący i wynika z chęci uwzględnienia flag kompilacji warunkowej, które nie powodują błędów kompilatora (z JLS):
Można oczekiwać, że instrukcja if będzie przetwarzana w następujący sposób:
Instrukcja if-then może normalnie wypełnić, jeśli spełniony jest przynajmniej jeden z poniższych warunków:
Instrukcja if-then jest osiągalna, a wyrażenie warunkowe nie jest wyrażeniem stałym, którego wartość jest prawdziwa.
Oświadczenie wtedy może zostać wypełnione normalnie.
Oświadczenie wtedy jest osiągalne, jeśli oświadczenie if-then jest osiągalne, a wyrażenie warunkowe nie jest wyrażeniem stałym, którego wartość jest fałszywa.
Instrukcja if-then-else może zostać wykonana normalnie, jeśli instrukcja następnie może zakończyć się normalnie lub instrukcja else może zakończyć się normalnie.
Oświadczenie then jest osiągalne, jeśli osiągalne jest wyrażenie if-then-else, a wyrażenie warunkowe nie jest wyrażeniem stałym, którego wartość jest fałszywa.
Instrukcja else jest osiągalna, jeśli jest osiągalna instrukcja if-then-else, a wyrażenie warunkowe nie jest wyrażeniem stałym, którego wartość jest prawdziwa.
Takie podejście byłoby spójne z traktowaniem innych struktur kontrolnych. Jednak aby umożliwić wygodne korzystanie z instrukcji if do celów „kompilacji warunkowej”, rzeczywiste reguły są różne.
Na przykład poniższa instrukcja powoduje błąd podczas kompilacji:
while (false) { x=3; }
ponieważ oświadczenie x=3;
nie jest osiągalne; ale pozornie podobny przypadek:
if (false) { x=3; }
nie powoduje błędu czasu kompilacji. Kompilator optymalizujący może zdać sobie sprawę, że instrukcja x=3;
nigdy nie zostanie wykonana i może pominąć kod tej instrukcji z wygenerowanego pliku klasy, ale instrukcjax=3;
nie jest uważana za „nieosiągalną” w technicznie określonym tutaj znaczeniu.
Uzasadnieniem tego odmiennego traktowania jest umożliwienie programistom zdefiniowania „zmiennych zmiennych”, takich jak:
static final boolean DEBUG = false;
a następnie napisz kod taki jak:
if (DEBUG) { x=3; }
Chodzi o to, że powinna istnieć możliwość zmiany wartości DEBUG z false na true lub z true na false, a następnie poprawnie skompiluj kod bez żadnych innych zmian w tekście programu.
Dlaczego warunkowa instrukcja break powoduje błąd kompilatora?
Jak podano w regułach osiągalności pętli, pętla while może również zakończyć się normalnie, jeśli zawiera osiągalną instrukcję break. Ponieważ zasady dotyczące osiągalności if
oświadczenia następnie klauzuli nie biorą stan if
pod uwagę w ogóle, taka warunkowa if
oświadczenie jest następnie klauzula jest zawsze uważane za osiągalne.
Jeśli break
jest osiągalny, to kod po pętli jest ponownie uznawany za osiągalny. Ponieważ nie ma dostępnego kodu, który spowodowałby nagłe zakończenie po pętli, metoda jest następnie uważana za zdolną do ukończenia normalnie, a więc kompilator oznacza ją jako błąd.