Бесконечные циклы в Java - PullRequest
       71

Бесконечные циклы в Java

82 голосов
/ 20 декабря 2011

Посмотрите на следующий бесконечный цикл 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'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 встречается до него внутри самого блока try.

Ответы [ 15 ]

105 голосов
/ 20 декабря 2011

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

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

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

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

55 голосов
/ 20 декабря 2011

Согласно спецификациям , о выражениях while говорится следующее.

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

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

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

14 голосов
/ 20 декабря 2011

Поскольку true - это константа, а b можно изменить в цикле.

10 голосов
/ 20 декабря 2011

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

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

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

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

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

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

6 голосов
/ 20 декабря 2011

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

4 голосов
/ 20 декабря 2011

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

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

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

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

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

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

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

4 голосов
/ 20 декабря 2011

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

3 голосов
/ 20 декабря 2011

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

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

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

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

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

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

3 голосов
/ 20 декабря 2011

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

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

PS, Lint Tool определенно должен сделать это за вас.

3 голосов
/ 20 декабря 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...