Spójrz na następującą nieskończoną while
pętlę w Javie. Powoduje to błąd w czasie kompilacji dla instrukcji poniżej.
while(true) {
System.out.println("inside while");
}
System.out.println("while terminated"); //Unreachable statement - compiler-error.
Poniższa sama nieskończona while
pętla działa jednak dobrze i nie powoduje żadnych błędów, w których właśnie zastąpiłem warunek zmienną boolowską.
boolean b=true;
while(b) {
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
Również w drugim przypadku instrukcja po pętli jest oczywiście nieosiągalna, ponieważ zmienna boolowska b
jest prawdziwa, a kompilator w ogóle nie narzeka. Czemu?
Edycja: Następująca wersja while
utknęła w nieskończonej pętli, co jest oczywiste, ale nie powoduje błędów kompilatora dla instrukcji poniżej, mimo że if
warunek w pętli jest zawsze false
iw konsekwencji pętla nigdy nie może powrócić i może zostać określona przez kompilator w sam czas kompilacji.
while(true) {
if(false) {
break;
}
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
while(true) {
if(false) { //if true then also
return; //Replacing return with break fixes the following error.
}
System.out.println("inside while");
}
System.out.println("while terminated"); //Compiler-error - unreachable statement.
while(true) {
if(true) {
System.out.println("inside if");
return;
}
System.out.println("inside while"); //No error here.
}
System.out.println("while terminated"); //Compiler-error - unreachable statement.
Edycja: to samo z if
i while
.
if(false) {
System.out.println("inside if"); //No error here.
}
while(false) {
System.out.println("inside while");
// Compiler's complain - unreachable statement.
}
while(true) {
if(true) {
System.out.println("inside if");
break;
}
System.out.println("inside while"); //No error here.
}
Następująca wersja while
również utknęła w nieskończonej pętli.
while(true) {
try {
System.out.println("inside while");
return; //Replacing return with break makes no difference here.
} finally {
continue;
}
}
To dlatego, że finally
blok jest zawsze wykonywany, mimo że return
instrukcja napotyka go wcześniej w samym try
bloku.
Odpowiedzi:
Kompilator może łatwo i jednoznacznie udowodnić, że pierwsze wyrażenie zawsze skutkuje nieskończoną pętlą, ale dla drugiego nie jest to takie łatwe. W twoim przykładzie zabawki jest to proste, ale co jeśli:
Kompilator najwyraźniej nie sprawdza prostszego przypadku, ponieważ całkowicie rezygnuje z tej drogi. Czemu? Ponieważ specyfikacja jest o
wiele trudniejsza. Patrz sekcja 14.21 :(Nawiasem mówiąc, mój kompilator nie skarżą się, gdy zmienna jest zadeklarowana
final
).źródło
Zgodnie ze specyfikacjami , o oświadczeniach while mówi się o tym.
Tak więc kompilator powie, że kod następujący po instrukcji while jest nieosiągalny, jeśli warunek while jest stałą o wartości true lub jeśli w tym czasie znajduje się instrukcja break. W drugim przypadku, ponieważ wartość b nie jest stała, kod następujący po niej nie jest uznawany za nieosiągalny. Za tym łączem znajduje się znacznie więcej informacji, aby uzyskać więcej informacji na temat tego, co jest, a co nie jest uważane za nieosiągalne.
źródło
Ponieważ prawda jest stała, a b można zmienić w pętli.
źródło
Ponieważ analiza stanu zmiennej jest trudna, więc kompilator po prostu się poddał i pozwolił ci robić, co chcesz. Ponadto specyfikacja języka Java zawiera jasne zasady dotyczące tego, jak kompilator może wykryć nieosiągalny kod .
Istnieje wiele sposobów na oszukanie kompilatora - inny typowy przykład to
public void test() { return; System.out.println("Hello"); }
co by nie zadziałało, ponieważ kompilator zdałby sobie sprawę, że obszar jest nie do naprawienia. Zamiast tego możesz to zrobić
public void test() { if (2 > 1) return; System.out.println("Hello"); }
To zadziałałoby, ponieważ kompilator nie jest w stanie zrozumieć, że wyrażenie nigdy nie będzie fałszywe.
źródło
if
różni się nieco od awhile
, ponieważ kompilator skompiluje nawet sekwencjęif(true) return; System.out.println("Hello");
bez narzekania. IIRC, jest to specjalny wyjątek w JLS. Kompilator byłby w stanie wykryć nieosiągalny kod poif
tak łatwym, jak zwhile
.Ta ostatnia nie jest nieosiągalna. Wartość logiczna b nadal może zostać zmieniona na fałsz gdzieś wewnątrz pętli, powodując warunek końcowy.
źródło
Domyślam się, że zmienna "b" ma możliwość zmiany swojej wartości, więc myśl kompilatora
System.out.println("while terminated");
może zostać osiągnięta.źródło
Kompilatory nie są doskonałe - ani nie powinny
Obowiązkiem kompilatora jest potwierdzenie składni, a nie potwierdzenie wykonania. Kompilatory mogą ostatecznie wychwycić i zapobiec wielu problemom w czasie wykonywania w języku z silną typizacją - ale nie są w stanie wychwycić wszystkich takich błędów.
Praktycznym rozwiązaniem jest posiadanie baterii testów jednostkowych w celu uzupełnienia sprawdzeń kompilatorów LUB użycie komponentów zorientowanych obiektowo do implementacji logiki, o której wiadomo, że jest niezawodna, zamiast polegać na pierwotnych zmiennych i warunkach zatrzymania.
Silne typowanie i OO: zwiększenie wydajności kompilatora
Niektóre błędy mają charakter składniowy - aw Javie mocne pisanie sprawia, że można złapać wiele wyjątków w czasie wykonywania. Ale używając lepszych typów, możesz pomóc kompilatorowi wymusić lepszą logikę.
Jeśli chcesz, aby kompilator skuteczniej wymuszał logikę, w Javie rozwiązaniem jest zbudowanie solidnych, wymaganych obiektów, które mogą wymusić taką logikę, i użycie tych obiektów do zbudowania aplikacji, a nie prymitywów.
Klasycznym tego przykładem jest użycie wzorca iteratora w połączeniu z pętlą foreach w Javie, ta konstrukcja jest mniej podatna na typ błędu, który ilustrujesz, niż uproszczona pętla while.
źródło
Kompilator nie jest wystarczająco zaawansowany, aby przeglądać wartości, które
b
mogą zawierać (chociaż przypisuje się je tylko raz). Pierwszy przykład jest łatwy do zrozumienia dla kompilatora, że będzie to nieskończona pętla, ponieważ warunek nie jest zmienny.źródło
Dziwię się, że Twój kompilator odmówił skompilowania pierwszego przypadku. Wydaje mi się to dziwne.
Ale drugi przypadek nie jest zoptymalizowany do pierwszego przypadku, ponieważ (a) inny wątek może zaktualizować wartość
b
(b) wywoływana funkcja może zmodyfikować wartośćb
jako efekt uboczny.źródło
Właściwie nie sądzę, żeby ktokolwiek zrozumiał to DOKŁADNIE dobrze (przynajmniej nie w sensie oryginalnego pytającego). OQ ciągle wspomina:
Ale to nie ma znaczenia, ponieważ ostatnia linia JEST osiągalna. Gdybyś wziął ten kod, skompilował go do pliku klasy i przekazał plik klasy komuś innemu (powiedzmy jako biblioteka), mogliby połączyć skompilowaną klasę z kodem, który modyfikuje „b” poprzez odbicie, opuszczając pętlę i powodując ostatnią linia do wykonania.
Dotyczy to każdej zmiennej, która nie jest stałą (lub finalnej, która kompiluje się do stałej w miejscu, w którym jest używana - czasami powoduje dziwne błędy, jeśli przekompilujesz klasę z wersją final, a nie klasą, która się do niej odwołuje, odwołanie klasa będzie nadal przechowywać starą wartość bez żadnych błędów)
Użyłem zdolności refleksji, aby zmodyfikować nie ostateczne zmienne prywatne innej klasy, aby małpa załatać klasę w zakupionej bibliotece - naprawiłem błąd, abyśmy mogli kontynuować rozwój, czekając na oficjalne poprawki od dostawcy.
Nawiasem mówiąc, w dzisiejszych czasach może to nie działać - chociaż robiłem to wcześniej, jest szansa, że taka mała pętla zostanie zapisana w pamięci podręcznej procesora, a ponieważ zmienna nie jest oznaczona jako ulotna, buforowany kod może nigdy nie być podnieś nową wartość. Nigdy nie widziałem tego w akcji, ale uważam, że jest to teoretycznie prawda.
źródło
b
jest to zmienna metody. Czy naprawdę możesz zmodyfikować zmienną metody za pomocą odbicia? Bez względu na ogólny punkt widzenia nie powinniśmy zakładać, że ostatnia linia jest nieosiągalna.Dzieje się tak po prostu dlatego, że kompilator nie zajmuje się zbytnio opieką nad dzieckiem, chociaż jest to możliwe.
Pokazany przykład jest prosty i rozsądny dla kompilatora do wykrywania nieskończonej pętli. Ale co powiesz na wstawienie 1000 linii kodu bez żadnego związku ze zmienną
b
? A co z tymi wszystkimi stwierdzeniamib = true;
? Kompilator z pewnością może ocenić wynik i powiedzieć, że ostatecznie wwhile
pętli jest to prawda , ale jak wolno będzie kompilować prawdziwy projekt?PS, narzędzie lint zdecydowanie powinno zrobić to za Ciebie.
źródło
Z punktu widzenia kompilatora
b
inwhile(b)
może gdzieś zmienić się na false. Kompilator po prostu nie zawraca sobie głowy sprawdzaniem.Na próbie zabawy
while(1 < 2)
,for(int i = 0; i < 1; i--)
etc.źródło
Wyrażenia są obliczane w czasie wykonywania, więc zastępując wartość skalarną „true” czymś w rodzaju zmiennej boolowskiej, zmieniasz wartość skalarną na wyrażenie boolowskie, a zatem kompilator nie ma możliwości poznania jej w czasie kompilacji.
źródło
Jeśli kompilator może jednoznacznie określić, że wartość logiczna zostanie oszacowana
true
w czasie wykonywania, zgłosi ten błąd. Kompilator zakłada, że zadeklarowana zmienna może zostać zmieniona (chociaż wiemy, że jako ludzie, nie będzie).Aby podkreślić ten fakt, jeśli zmienne są zadeklarowane tak jak
final
w Javie, większość kompilatorów zgłosi ten sam błąd, co w przypadku podstawienia wartości. Dzieje się tak, ponieważ zmienna jest definiowana w czasie kompilacji (i nie można jej zmienić w czasie wykonywania), a kompilator może w związku z tym jednoznacznie określić, że wyrażenie jest obliczanetrue
w czasie wykonywania.źródło
Pierwsza instrukcja zawsze skutkuje nieskończoną pętlą, ponieważ określiliśmy stałą w warunku pętli while, gdzie tak jak w drugim przypadku kompilator zakłada, że istnieje możliwość zmiany wartości b wewnątrz pętli.
źródło