Бесконечные петли в 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 истинна, но компилятор вообще не жалуется. Почему?


Изменить: Следующая версия 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.

Изменить: То же самое с if и while.

if(false) {
    System.out.println("inside if"); //No error here.
}

while(false) {
    System.out.println("inside while");
    // Compiler 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 встречается перед ним внутри самого блока try.

Ответ 1

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

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

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

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

Ответ 2

В соответствии с спецификациями говорится о инструкциях while.

Оператор while может завершиться нормально, если хотя бы один из верно следующее:

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

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

Ответ 3

Поскольку true является константой, а b может быть изменено в цикле.

Ответ 4

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

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

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

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

public void test()
{
    if (2 > 1) return;
    System.out.println("Hello");
}

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

Ответ 5

Последнее не недостижимо. Boolean b все еще имеет возможность быть измененным на false где-то внутри цикла, вызывая условие окончания.

Ответ 6

Мое предположение, что переменная "b" имеет возможность изменить ее значение, поэтому компилятор думает System.out.println("while terminated"); может быть достигнуто.

Ответ 7

Компиляторы не идеальны - и не должны быть

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

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

Сильная типизация и OO: повышение эффективности компилятора

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

Если вы хотите, чтобы компилятор более эффективно применял логику, в Java это решение должно создавать надежные, необходимые объекты, которые могут обеспечить такую ​​логику, и использовать эти объекты для создания вашего приложения, а не примитивов.

Классическим примером этого является использование шаблона итератора в сочетании с циклом Java foreach, эта конструкция менее уязвима к типу ошибки, которую вы показываете, чем простой цикл while.

Ответ 8

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

Ответ 9

Я удивлен, что ваш компилятор отказался компилировать первый случай. Мне это кажется странным.

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

Ответ 10

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

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

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

Ответ 11

На самом деле я не думаю, что кто-то получил это QUITE правильно (по крайней мере, не в оригинальном вопросе). OQ продолжает упоминать:

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

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

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

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

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

Ответ 12

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

Для удовольствия попробуйте while(1 < 2), for(int i = 0; i < 1; i--) и т.д.

Ответ 13

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

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

Ответ 14

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

Ответ 15

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