Почему перехват проверенных исключений разрешен для кода, который не генерирует исключения?
В Java методы, которые выдают проверенные исключения ( Exception или его подтипы - IOException, InterruptedException и т. Д.), Должны объявлять оператор throws:
public abstract int read() throws IOException;
Методы, которые не объявляют throws
оператор не может генерировать проверенные исключения.
public int read() { // does not compile
throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown
Но перехват проверенных исключений в безопасных методах все еще разрешен в Java:
public void safeMethod() { System.out.println("I'm safe"); }
public void test() { // method guarantees not to throw checked exceptions
try {
safeMethod();
} catch (Exception e) { // catching checked exception java.lang.Exception
throw e; // so I can throw... a checked Exception?
}
}
Вообще-то, нет. Это немного забавно: компилятор знает, что e не является проверенным исключением, и позволяет отбросить его. Вещи даже немного смешны, этот код не компилируется:
public void test() { // guarantees not to throw checked exceptions
try {
safeMethod();
} catch (Exception e) {
throw (Exception) e; // seriously?
}
}
// Error: unreported exception java.lang.Exception; must be caught or declared to be thrown
Первый фрагмент был мотивом для вопроса.
Компилятор знает, что проверенные исключения не могут быть выброшены в безопасный метод - поэтому, возможно, он должен позволять перехватывать только непроверенные исключения?
Возвращаясь к основному вопросу - есть ли причины для реализации перехвата проверенных исключений таким образом? Это просто недостаток в дизайне или я упускаю некоторые важные факторы - возможно, обратную несовместимость? Что может пойти не так, если только RuntimeException
было разрешено быть пойманным в этом сценарии? Примеры очень ценятся.
3 ответа
Цитирование спецификации языка Java, §11.2.3:
Это ошибка времени компиляции, если предложение catch может перехватить проверенный класс исключений E1, и это не тот случай, когда блок try, соответствующий предложению catch, может выбросить проверенный класс исключений, который является подклассом или суперклассом E1, если только E1 не является Исключение или суперкласс Исключения.
Я предполагаю, что это правило возникло задолго до Java 7, где не было многократных ловушек. Поэтому, если у вас был try
блок, который может генерировать множество исключений, самый простой способ поймать все - это поймать общий суперкласс (в худшем случае Exception
, или же Throwable
если хочешь поймать Error
а также).
Обратите внимание, что вы не можете поймать тип исключения, который совершенно не связан с тем, что на самом деле выдается - в вашем примере, перехват любого подкласса Throwable
это не RuntimeException
будет ошибка:
try {
System.out.println("hello");
} catch (IOException e) { // compilation error
e.printStackTrace();
}
Редактирование OP: Основная часть ответа заключается в том, что примеры вопросов работают только для класса исключений. Обычно отлов проверенных исключений не допускается в случайных местах кода. Извините, если я кого-то запутал, используя эти примеры.
В Java 7 введена более инклюзивная проверка типов исключений.
Однако в Java SE 7 вы можете указать типы исключений FirstException и SecondException в предложении throws в объявлении метода rethrowException. Компилятор Java SE 7 может определить, что исключение, генерируемое оператором throw e, должно быть получено из блока try, и единственными исключениями, генерируемыми блоком try, могут быть FirstException и SecondException.
Этот отрывок говорит о try
блок который конкретно кидает FirstException
а также SecondException
; хотя catch
броски блока Exception
, метод должен только объявить, что он бросает FirstException
а также SecondException
не Exception
:
public void rethrowException(String exceptionName) throws FirstException, SecondException { try { // ... } catch (Exception e) { throw e; } }
Это означает, что компилятор может обнаружить, что выбрасываются только возможные типы исключений test
являются Error
с или RuntimeException
s, ни один из которых не должен быть пойман. Когда ты throw e;
, это может сказать, даже когда статический тип Exception
, что это не должно быть объявлено или повторно поймано.
Но когда вы бросаете его Exception
это обходит эту логику. Теперь компилятор воспринимает это как обычный Exception
который должен быть пойман или объявлен.
Основная причина добавления этой логики в компилятор состояла в том, чтобы позволить программисту указывать только определенные подтипы в throws
пункт при свержении генерала Exception
ловить эти конкретные подтипы. Тем не менее, в этом случае это позволяет поймать общий Exception
и не должны объявлять какие-либо исключения в throws
предложение, потому что никакие определенные типы, которые могут быть выброшены, не являются проверенными исключениями.
Проблема здесь заключается в том, что проверенные / непроверенные ограничения исключений влияют на то, что вашему коду разрешено генерировать, а не на то, что ему разрешено ловить. Хотя вы все еще можете поймать любой тип Exception
единственные, кого вам разрешено снова бросать, это непроверенные. (Вот почему преобразование вашего непроверенного исключения в проверенное исключение нарушает ваш код.)
Поймать непроверенное исключение с Exception
допустимо, потому что непроверенные исключения (ака RuntimeException
s) является подклассом исключения, и он следует стандартным правилам полиморфизма; это не превращает пойманное исключение в Exception
Будучи хранителем String
в Object
не превращает String
в Object
, Полиморфизм означает, что переменная, которая может содержать Object
может содержать все, что происходит от Object
(такой как String
). Точно так же, как Exception
суперкласс всех типов исключений, переменная типа Exception
может содержать любой класс, производный от Exception
без превращения объекта в Exception
, Учти это:
import java.lang.*;
// ...
public String iReturnAString() { return "Consider this!"; }
// ...
Object o = iReturnAString();
Несмотря на то, что тип переменной Object
, o
до сих пор хранит String
не так ли? Аналогично, в вашем коде:
try {
safeMethod();
} catch (Exception e) { // catching checked exception
throw e; // so I can throw... a checked Exception?
}
Что это означает на самом деле "поймать что-нибудь совместимое с классом Exception
(т.е. Exception
и все, что происходит от него)."Подобная логика используется и в других языках; например, в C++, перехватывая std::exception
также будет ловить std::runtime_error
, std::logic_error
, std::bad_alloc
любые правильно определенные пользовательские исключения и т. д., поскольку все они происходят из std::exception
,
tl;dr: Вы не ловите проверенные исключения, вы ловите любые исключения. Исключение становится проверенным, только если вы преобразуете его в проверенный тип исключения.