Недоступный код работает нормально - Как?

Из моего понимания следующий код, который я написал, не должен компилироваться, поскольку утверждение "Я недостижимо" после return.

Однако он компилируется абсолютно точно.

Также из JLS: Unreachable Statement нельзя компилировать его.

из спецификации, в 14.21 Недостижимые утверждения:

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

  • Блок try может завершиться нормально или любой блок catch может завершиться нормально.

  • Если оператор try имеет блок finally, тогда блок finally может завершиться нормально.

Здесь блок try не может закончить нормально, но блок catch может так же, как и блок finally, поэтому я запутался здесь

    public class Test1 {
     public static void main(String[] args) {
        try {
            return;

        } catch (Exception e) {
            System.out.println("catch");

        } finally {
            System.out.println("finally");
        }
        System.out.println("I am unreachable??!!!");
    }
}

Может ли кто-нибудь помочь мне понять это поведение?

Ответ 1

Я считаю, что это соответствующие цитаты из JLS 14.21:

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

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

    Первый оператор в непустом блоке, который не является блоком коммутатора, доступен, если блок доступен.

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

Так что ваши

System.out.println("I am unreachable??!!!");

оператор доступен, если if (что означает "если и только если"), оператор try может завершиться нормально, что приводит к следующей цитате:

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

    • Блок try может завершиться нормально или любой блок catch может завершиться нормально.

    • Если оператор try имеет блок finally, тогда блок finally может завершиться нормально.

Поскольку ваш блок catch может завершиться нормально, и у вас есть блок finally который может завершиться нормально, оператор try может завершиться нормально. Следовательно, System.out.println("I am unreachable??!!!"); выражение после него считается доступным, независимо от его return; внутри блока try.

Обратите внимание, or в

Блок try может завершиться нормально или любой блок catch может завершиться нормально.

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

Наконец, логика такого поведения:

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

Поэтому, поскольку у вас есть блок catch (Exception e) который может завершиться нормально, компилятор предполагает, что этот catch-блок он доступен, и поэтому весь оператор try может завершиться нормально, даже если блок try не может завершиться нормально.

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

Ответ 2

Вы вернулись в попытке.

Что делать, если есть исключение, и оно напрямую идет на улов. Следовательно, это не недостижимо с точки зрения компилятора и успешно компилируется.

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

Также, согласно JLS 14.21:

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

См. Вывод ниже, когда вы возвращаетесь как в попытке, так и в catch:

jshell>  public class Test1 {
   ...>     public static void main(String[] args) {
   ...>         try {
   ...>             return;
   ...>
   ...>         } catch (Exception e) {
   ...>             return;
   ...>
   ...>         }
   ...>
   ...>         System.out.println("I am unreachable??!!!");
   ...>     }
   ...> }
|  Error:
|  unreachable statement
|          System.out.println("I am unreachable??!!!");
|          ^------------------------------------------^

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

Попытка сообщения будет считаться достижимой, если:

1) Try has a return statement with catch and finally not having return statement
2) Try does not have a return statement with catch having or not having return statement and finally not having return statement
3) Try, catch and finally not having return statement

Ответ 3

Попытка дать более упрощенную причину проблемы, код доступен, в случае исключения в блоке try. В этом случае управление далее переходит к блоку catch, а затем к блоку finally. После окончательного блока будет выполняться конкретный оператор.

try {
            return;                                 //line 1

        } catch (Exception e) {
            System.out.println("catch");            //line 2

        } finally {
            System.out.println("finally");          //line 3
        }
        System.out.println("I am unreachable??!!"); //line 4

Это означает, что существует 2 случая, следовательно 2 потока:

  1. строка 1 → строка 3 → возврат (в случае отсутствия исключения)
  2. строка 1 (происходит исключение) → строка 2 → строка 3 → строка 4 (если попытка получает исключение)

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

  1. возврат из блока catch
  2. вернуться из блока finally.

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

try {
            return;                                 //line 1

        } catch (Exception e) {
            System.out.println("catch");            //line 2
            return;                                 //return control
        } finally {
            System.out.println("finally");          //line 3
            return;                                 //or return from here
        }
        System.out.println("I am unreachable??!!"); //line 4    

Надеюсь, теперь это дает четкую картину фактической причины проблемы.

Ответ 4

Когда вы смотрите на "недостижимые утверждения" в Java-программе, то, что считает определение в языке, а не то, что может найти умный компилятор.

Согласно языку Java, последний println не является недостижимым. Хотя, глядя на код, легко (умному человеку) понять, что он никогда не может быть выполнен.

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