Определён ли формальный повторный забрасывание при многократной ловле?

Почему throw e разрешено, а throw a нет в следующем случае?

  void test() {
    try {
      System.out.println();
    } catch (Error | RuntimeException e) {
      var a = e;
      //throw a; unreported exception Throwable; must be caught or declared to be thrown
      throw e;
    }
  }

Все это интуитивно имеет смысл, но с JLS 14.20

Предложение multi-catch можно рассматривать как последовательность предложений uni-catch. То есть предложение catch, в котором тип параметра исключения обозначается как объединение D1|D2|...|Dn, эквивалентно последовательности из n предложений catch, где типы параметров исключения являются типами классов D1, D2, ..., Dn соответственно. В блоке каждого из n предложений catch объявленный тип параметра исключения — lub(D1, D2, ..., Dn).

Поскольку lub(Error,RuntimeException) — это Throwable, приведенный выше код должен быть эквивалентен:

    try {
      System.out.println();
    } catch (Error e) {
      Throwable lub = e;
      throw lub;
    } catch (RuntimeException e) {
      Throwable lub = e;
      throw lub;
    }

(который, очевидно, не компилируется)

Более того, тип a является типом e, «когда он рассматривается так, как если бы он не появлялся в контексте присваивания» (JLS 14.4.1), но, как показано выше, это не то же самое, что тип e.

Есть ли что-нибудь, что я упустил из виду?


Редактировать: это не дубликат Почему в определенных случаях разрешено повторное создание Throwable без его объявления? потому что этот вопрос специфичен для multi-catch (который там не обсуждается) и возникает из-за непонимания конкретного фрагмента JLS, который касается multi-catch. Ответы, представленные в этом вопросе, помогли мне соединить точки :)

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
0
73
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Компилятор Java достаточно умен, чтобы распознать, что перехват e и выброс e будет означать, что вы выбрасываете RuntimeException или Error, и, следовательно, это не проверяемое исключение, которое не объявлено.

С другой стороны, тип var a выводится для Throwable (а не для Error | RuntimeException, как вы можете подумать, поскольку такое объявление на самом деле недействительно в этом контексте!), и, таким образом, throw a означает, что вы бросаете Throwable, и поэтому компилятор не знает наверняка, что вы не генерируете проверенное исключение, и поэтому вызывает ошибку.

Можно ли изменить компилятор, чтобы признать это? Наверное, да. Имеет ли смысл это сделать? Вероятно, нет (или, по крайней мере, ценность действий, вероятно, не стоит вложений).

Это следует из правил, указанных в 14.18 Оператор throw в сочетании с 11.2.2 Анализ исключений операторов.

В частности, это следует из этого правила в 11.2.2 для throw e:

Утверждение throw, бросаемое выражение которого является окончательным или фактически последний параметр исключения предложения catch C может вызвать исключение класс E, если:

  • E — это класс исключений, который может генерировать блок try инструкции try, объявляющей C; и
  • E является присваиванием, совместимым с любым из перехватываемых классов исключений C; и
  • E не совместим по присваиванию ни с одним из перехватываемых классов исключений предложений catch, объявленных слева от C в той же попытке. заявление.

С другой стороны, для throw a применимо следующее:

Оператор throw (§14.18), чье выброшенное выражение имеет статический тип E и не является окончательным или фактически окончательным параметром исключения, может выдать E или любой класс исключения, который может выдать выброшенное выражение.

Важной частью JLS является 11.2.2, в котором говорится:

Утверждение throw, бросаемое выражение которого представляет собой final или эффективно последний параметр исключения в предложении catch C может выдать исключение класс E, если:

  • E — это класс исключения, который может выдать блок try оператора try, объявляющего C; и

  • E является присваиванием, совместимым с любым из перехватываемых классов исключений C; и

  • ...

Это объясняет, почему следующие команды выбрасывают либо Error, либо RuntimeException:

  void test1() {
    try {
      ...
    } catch (Error | RuntimeException e) {
      throw e;
    }
  }

Но в следующем:

  void test2() {
    try {
      ...
    } catch (Error | RuntimeException e) {
      var a = e;
      throw a;
    }
  }

предполагаемый тип a — это Throwable (верхняя граница). И это приведет к ошибке компиляции... если включающий метод не объявлен как бросающий Throwable.

Для объявления varJLS 14.4.1 говорит следующее:

Если LocalVariableType равен var, то пусть T будет типом выражения инициализатора, если рассматривать его так, как если бы оно не появлялось в контексте присваивания и, таким образом, было автономным выражением (§15.2). Тип локальной переменной — это проекция T вверх по отношению ко всем переменным синтетического типа, упомянутым T (раздел 4.10.5).

Это сложно разобрать, но я понимаю, что «проекция Т вверх» — это тот же тип, который называется типом «наименьшей верхней границы». В примере test2 это будет Throwable.

Обратите внимание, что текст JLS, который вы процитировали в своем вопросе, является иллюстративным, а не нормативным. И он не описывает, что происходит, когда вы (повторно) выбрасываете перехваченное исключение.

Ответ принят как подходящий

Это описано в 11.2.2. По сути, это сводится к тому, что «параметры исключения в catch (...) являются особым случаем».

Наша цель — показать throw e; компиляции. Мы начинаем с этой строки в 11.2.3:

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

Итак, мы хотим показать, что оператор try в test не может выбрасывать Throwable. Обратите внимание, что «может бросить» в этом контексте — это строго определенный термин. Согласно 11.2.2,

Оператор try может вызвать исключение класса E, если:

  • Блок try может выдать E, [...]

  • Некоторый блок catch оператора try может выдать E, и либо блокfinally отсутствует, либо блокfinally может завершиться нормально; или

  • Блок «finally» присутствует и может выдать E.

Здесь важен только второй пункт. Давайте покажем, что блок catch не может выбросить Throwable. А именно throw e; не может бросить Throwable.

Оператор throw, выражение которого является окончательным или фактически последний параметр исключения в предложении catch C может выдать исключение класс E, если:

  • E — это класс исключений, который может генерировать блок try инструкции try, объявляющей C; и

  • E является присваиванием, совместимым с любым из перехватываемых классов исключений C; и

  • E не совместим по присваиванию ни с одним из перехватываемых классов исключений предложений catch, объявленных слева от C в той же попытке. заявление.

throw e; здесь сразу же не соответствует первому пункту, поэтому мы можем сделать вывод, что throw e; не может бросить Throwable.

Если бы это было throw a;, то вышеизложенное не применимо, но применимо другое предложение:

Оператор throw, чье выброшенное выражение имеет статический тип E и не является окончательным или фактически окончательным параметром исключения, может выдать E или любой класс исключения, который может выдать выброшенное выражение.

Другие вопросы по теме