Бесконечные циклы в 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 81

15 ответов:

компилятор может легко и однозначно доказать, что первое выражение всегда приводит к бесконечному циклу, но это не так просто для второго. В вашем игрушечном примере это просто, но что, если:

  • содержимое переменной считывалось из файла?
  • переменная не была локальной и могла быть изменена другим потоком?
  • переменная полагалась на некоторый пользовательский ввод?

компилятор явно не проверка ваш более простой случай, потому что он полностью отказывается от этой дороги. Зачем? Потому что это гораздо сложнее Запрещенные спец. Смотрите 14.21:

(кстати, мой компилятор тут жалуйтесь, когда переменная объявлена final.)

по технические характеристики, о высказываниях while говорится следующее.

некоторое время оператор может обычно завершать эквивалентность, по крайней мере один из верно следующее:

  • while оператор достижим, и выражение условия не является константным выражением со значением true.
  • существует достижимый оператор break, который выходит из оператора while.\

Так, компилятор будет только сказать, что код после оператора while недоступен, если условие while является константой с истинным значением, или есть оператор break в то время как. Во втором случае, поскольку значение b не является константой, он не считает код, следующий за ним, недостижимым. За этой ссылкой есть намного больше информации, чтобы дать вам более подробную информацию о том, что есть и что не считается недостижимым.

потому что true является постоянным и b может быть изменен в цикле.

потому что анализировать состояние переменной сложно, поэтому компилятор в значительной степени просто сдался и позволил вам делать то, что вы хотите. Кроме того, спецификация языка Java имеет четкие правила о как компилятор может обнаружить недостижимый код.

есть много способов, чтобы обмануть компилятор - еще один распространенный пример

public void test()
{
    return;
    System.out.println("Hello");
}

который не будет работать, так как компилятор поймет, что область была недоступна. Вместо этого, вы могли бы делай

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 как побочный эффект.

на самом деле я не думаю, что кто-то получил это совершенно правильно (по крайней мере, не в первоначальном смысле вопрошающего). В ОК говорят:

правильно, но не имеет значения, так как b не изменяется в цикле

но это не имеет значения, потому что последняя строка доступна. Если вы взяли этот код, скомпилировали его в файл класса и передали файл класса кому-то другому (скажем, как библиотеку), они могут связать скомпилированный класс с кодом, который изменяет "b" через отражение, выход из цикла и выполнение последней строки.

Это верно для любой переменной, которая не является константой (или final, которая компилируется в константу в том месте, где она используется-иногда вызывая странные ошибки, если вы перекомпилируете класс с final, а не класс, который ссылается на него, ссылочный класс все равно будет содержать старое значение без каких-либо ошибок)

Я использовал способность отражения для изменения не окончательных частных переменных еще один класс для обезьян-патч класса в приобретенной библиотеке-исправление ошибки, чтобы мы могли продолжать разработку, пока мы ждем официальных патчей от поставщика.

кстати, это может на самом деле не работать в эти дни-хотя я делал это раньше, есть шанс, что такой небольшой цикл будет кэшироваться в кэше процессора, и поскольку переменная не помечена volatile, кэшированный код никогда не сможет получить новое значение. Я никогда не видел этого в действии, но я считаю, что это теоретически истинный.

это просто потому, что компилятор не слишком много работы по уходу за ребенком, хотя это возможно.

приведенный пример является простым и разумным для компилятора, чтобы обнаружить бесконечный цикл. Но как насчет того, чтобы вставить 1000 строк кода без какой-либо связи с переменной b? А как насчет тех утверждений, которые все b = true;? Компилятор определенно может оценить результат и сказать вам, что это правда в конечном итоге в while цикл, но как медленно он будет компилировать реальный проект?

PS, инструмент lint определенно должен сделать это за вас.

С точки зрения компилятора это b на while(b) может измениться на false где-то. Компилятор просто не утруждает себя проверкой.

для начала попробовать while(1 < 2),for(int i = 0; i < 1; i--) etc.

выражения вычисляются во время выполнения, поэтому при замене скалярного значения "true" чем-то вроде булевой переменной вы изменили скалярное значение в булево выражение и, таким образом, компилятор не может узнать его во время компиляции.

если компилятор может окончательно определить, что логическое значение будет равно true во время выполнения он выдаст эту ошибку. Компилятор предполагает, что переменная, которую вы объявили можете изменить (хотя мы знаем, что здесь, как и люди ее не будет).

чтобы подчеркнуть этот факт, если переменные объявлены как final в Java большинство компиляторов выдадут ту же ошибку, что и при замене значения. Это связано с тем, что переменная определяется во время компиляции (и не может быть изменены во время выполнения) и поэтому компилятор может окончательно определить, что выражение true во время выполнения.

первый оператор всегда приводит к бесконечному циклу, потому что мы указываем константу в состоянии цикла while, где, как и во втором случае компилятор предполагает, что существует возможность изменения значения b внутри цикла.