Бесконечные циклы в Java
посмотрите на следующую бесконечность while
цикл в Java. Это вызывает ошибку времени компиляции для инструкции ниже.
while(true) {
System.out.println("inside while");
}
System.out.println("while terminated"); //Unreachable statement - compiler-error.
следующий же бесконечное while
цикл, однако работает нормально и не выдает ошибок, в которых я просто заменил условие булевой переменной.
boolean b=true;
while(b) {
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
во втором случае также оператор после цикла, очевидно, недоступен, потому что булева переменная b
правда до сих пор компилятор вообще не жалуется. Почему?
Edit: следующая версия while
застревает в бесконечном цикле, как очевидно, но не выдает ошибок компилятора для оператора ниже него, хотя if
условие в цикле всегда false
и, следовательно, цикл никогда не может вернуться и может быть определен компилятором во время компиляции себя.
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.
Edit: то же самое и с if
и 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.
}
следующая версия while
также застревает в бесконечном цикле.
while(true) {
try {
System.out.println("inside while");
return; //Replacing return with break makes no difference here.
} finally {
continue;
}
}
это так finally
блок всегда выполняется, даже если return
заявление встречается перед ним в блок.
15 ответов
компилятор может легко и однозначно доказать, что первое выражение всегда приводит к бесконечному циклу, но это не так просто для второго. В вашем игрушечном примере это просто, но что, если:
- содержимое переменной считывалось из файла?
- переменная не была локальной и могла быть изменена другим потоком?
- переменная полагалась на некоторый пользовательский ввод?
компилятор явно не проверка ваш более простой случай, потому что он полностью отказывается от этой дороги. Почему? Потому что это гораздо сложнее Запрещенные спец. См.14.21:
(кстати, мой компилятор тут пожаловаться, когда переменная объявлена final
.)
по технические характеристики, следующее сказано о заявлениях while.
оператор while может выполнить нормально iff по крайней мере один из верно следующее:
- while оператор достижим, и выражение условия не является константным выражением со значением true.
- существует достижимый оператор break, который выходит из оператора while.\
Так, компилятор будет только говорить, что код, следующий за оператором while, недоступен, если условие while является константой с истинным значением или есть оператор break в while. Во втором случае, поскольку значение b не является константой, он не считает код, следующий за ним, недостижимым. За этой ссылкой есть намного больше информации, чтобы дать вам более подробную информацию о том, что такое и что не считается недостижимым.
потому что анализировать состояние переменной сложно, поэтому компилятор в значительной степени просто сдался и позволил вам делать то, что вы хотите. Кроме того, спецификация языка Java имеет четкие правила о как компилятору разрешено обнаруживать недостижимый код.
существует много способов обмануть компилятор-еще один распространенный пример -
public void test()
{
return;
System.out.println("Hello");
}
не будет работать, поскольку компилятор будет понимать, что этот район был unreacable. Вместо этого, вы могли бы do
public void test()
{
if (2 > 1) return;
System.out.println("Hello");
}
это будет работать, так как компилятор не может понять, что выражение никогда не будет ложным.
последнее не является недостижимым. Логическое b все еще имеет возможность быть измененным на false где-то внутри цикла, вызывая условие окончания.
Я предполагаю, что переменная " b " имеет возможность изменить свое значение, поэтому компилятор думает System.out.println("while terminated");
можно связаться.
компиляторы не идеальны-и не должны быть
ответственность компилятора заключается в подтверждении синтаксиса, а не в подтверждении выполнения. Компиляторы могут в конечном счете поймать и предотвратить многие проблемы во время выполнения на строго типизированном языке, но они не могут поймать все такие ошибки.
практическое решение состоит в том, чтобы иметь батареи модульных тестов, чтобы дополнить ваши проверки компиляторов или использовать объектно-ориентированные компоненты для реализации логики, которые, как известно, надежный, а не полагающийся на примитивные переменные и условия остановки.
сильная типизация и OO : повышение эффективности компилятора
некоторые ошибки являются синтаксическими по своей природе - и в Java сильная типизация делает много исключений времени выполнения уловимыми. Но, используя лучшие типы, вы можете помочь компилятору обеспечить лучшую логику.
Если вы хотите, чтобы компилятор более эффективно применял логику, в Java решение заключается в создании надежного, обязательные объекты, которые могут применять такую логику и использовать эти объекты для создания приложения, а не примитивы.
классическим примером этого является использование шаблона итератора в сочетании с циклом foreach Java эта конструкция менее уязвима для типа ошибки, которую вы иллюстрируете, чем упрощенный цикл while.
компилятор недостаточно сложен, чтобы выполнить значения, которые b
может содержать (хотя вы назначаете его только один раз). В первом примере компилятору легко увидеть, что это будет бесконечный цикл, потому что условие не является переменным.
Я удивлен, что ваш компилятор отказался компилировать первом случае. Мне это кажется странным.
но второй случай не оптимизирован для первого случая, потому что (a) другой поток может обновить значение b
(b) вызываемая функция может изменить значение b
как побочный эффект.
на самом деле я не думаю, что кто-то понял это правильно (по крайней мере, не в первоначальном смысле вопроса). OQ продолжает упоминать:
правильно, но не имеет значения, так как b не изменяется в цикле
но это не имеет значения, потому что последняя строка достижима. Если вы взяли этот код, скомпилировали его в файл класса и передали файл класса кому-то другому (скажем, как библиотеку), они могут связать скомпилированный класс с кодом, который изменяет "b" через отражение, выход из цикла и выполнение последней строки.
Это верно для любой переменной, которая не является константой (или final, которая компилируется с константой в том месте, где она используется-иногда вызывая причудливые ошибки, если вы перекомпилируете класс с final, а не класс, который ссылается на него, ссылочный класс по-прежнему будет содержать старое значение без каких-либо ошибок)
Я использовал способность отражения для изменения не конечных частных переменных еще один класс для обезьяньего исправления класса в приобретенной библиотеке-исправление ошибки, чтобы мы могли продолжать разработку, ожидая официальных исправлений от поставщика.
кстати, это может на самом деле не работать в эти дни-хотя я делал это раньше, есть вероятность, что такой небольшой цикл будет кэшироваться в кэше CPU, и поскольку переменная не отмечена изменчивой, кэшированный код никогда не сможет забрать новое значение. Я никогда не видел это в действии, но я верю, что это теоретически истинный.
это просто потому, что компилятор не слишком много работает с ребенком, хотя это возможно.
показанный пример прост и разумен для компилятора для обнаружения бесконечного цикла. Но как насчет вставки 1000 строк кода без каких-либо отношений с переменной b
? А как насчет тех утверждений, которые все b = true;
? Компилятор определенно может оценить результат и сказать вам, что это правда в конце концов в while
цикл, но как медленно он будет компилировать реальный проект?
PS, lint tool определенно должен сделать это за вас.
С точки зрения компилятора это b
на while(b)
может измениться на false где-нибудь. Компилятор просто не утруждает себя проверкой.
для начала попробовать while(1 < 2)
, for(int i = 0; i < 1; i--)
etc.
выражения оцениваются во время выполнения, поэтому при замене скалярного значения "true" на что-то вроде булевой переменной вы изменили скалярное значение на булевое выражение, и, таким образом, компилятор не может узнать его во время компиляции.
если компилятор может окончательно определить, что логическое значение будет равно true
во время выполнения он выдаст эту ошибку. Компилятор предполагает, что переменная, которую вы объявили can изменить (хотя мы знаем, что здесь, как и люди ее не будет).
чтобы подчеркнуть этот факт, если переменные объявлены как final
в Java большинство компиляторов выдадут ту же ошибку, что и при замене значения. Это связано с тем, что переменная определяется во время компиляции (и не может быть изменены во время выполнения) и поэтому компилятор может окончательно определить, что выражение true
во время выполнения.
первое утверждение всегда приводит к бесконечному циклу, потому что мы задаем константу в условии цикла while, где, как и во втором случае, компилятор предполагает, что существует возможность изменения значения b внутри цикла.