Что означает "ошибка: незарегистрированное исключение ; должно быть перехвачено или объявлено выброшенным" и как мне это исправить?
Начинающие Java-программисты часто сталкиваются с ошибками, сформулированными следующим образом:
"error: unreported exception <XXX>; must be caught or declared to be thrown"
где XXX - это имя некоторого класса исключений.
Пожалуйста, объясните:
- Что говорится в сообщении об ошибке компиляции,
- концепции Java, стоящие за этой ошибкой, и
- как это исправить.
Переведено автоматически
Ответ 1
Перво-наперво. Это ошибка компиляции, а не исключение. Вы должны увидеть это во время компиляции.
Если вы видите это в сообщении об исключении во время выполнения, это, вероятно, потому, что вы запускаете какой-то код с ошибками компиляции. Вернитесь и исправьте ошибки компиляции. Затем найдите и установите параметр в вашей IDE, который предотвращает генерацию файлов ".class" для исходного кода с ошибками компиляции. (Избавьте себя от лишних хлопот в будущем.)
Короткий ответ на вопрос таков:
В сообщении об ошибке говорится, что оператор с этой ошибкой создает (или распространяет) проверенное исключение, и исключение (the
XXX
) обрабатывается неправильно.Решение состоит в том, чтобы обработать исключение либо:
- перехват и обработка с помощью
try ... catch
заявления, или - объявляю, что заключающий метод или конструктор
throws
это1.
- перехват и обработка с помощью
1 - Есть несколько крайних случаев, когда вы не можете этого сделать. Прочитайте остальную часть ответа!
Проверенные и непроверенные исключения
В Java исключения представлены классами, происходящими от java.lang.Throwable
класса. Исключения делятся на две категории:
- Проверяемыми исключениями являются
Throwable
, andException
и его подклассы, кромеRuntimeException
и его подклассов. - Непроверенные исключения - это все другие исключения; т.е.
Error
и его подклассы, иRuntimeException
и его подклассы.
(Выше "подклассы" включают прямые и косвенные подклассы.)
Различие между проверяемыми и непроверяемыми исключениями заключается в том, что проверяемые исключения должны "обрабатываться" внутри заключающего их метода или конструктора, но с непроверяемыми исключениями нет необходимости иметь дело.
(Вопрос: Как узнать, проверено исключение или нет? A: Найдите javadoc для класса exception и посмотрите на его родительские классы.)
Как вы справляетесь с (проверенным) исключением
С точки зрения языка Java, есть два способа обработки исключения, которое "удовлетворит" компилятор:
Вы можете перехватить исключение в
try ... catch
инструкции. Например:public void doThings() {
try {
// do some things
if (someFlag) {
throw new IOException("cannot read something");
}
// do more things
} catch (IOException ex) {
// deal with it <<<=== HERE
}
}В приведенном выше примере мы поместили оператор, который выдает (checked)
IOException
в телеtry
. Затем мы написалиcatch
предложение для перехвата исключения. (Мы могли бы перехватить суперклассIOException
... но в данном случае это было быException
и перехватException
- плохая идея.)Вы можете объявить, что заключающий метод или конструктор
throws
исключениеpublic void doThings() throws IOException {
// do some things
if (someFlag) {
throw new IOException("cannot read something");
}
// do more things
}Выше мы объявили, что
doThings()
выдаетIOException
. Это означает, что любой код, вызывающийdoThings()
метод, должен иметь дело с исключением. Короче говоря, мы передаем проблему обработки исключения вызывающей стороне.
Что из этого нужно сделать правильно?
Это зависит от контекста. Однако общий принцип заключается в том, что вы должны иметь дело с исключениями на том уровне кода, где вы в состоянии справиться с ними надлежащим образом. И это, в свою очередь, зависит от того, что будет делать код обработки исключений (в HERE
). Может ли он восстановиться? Может ли он отказаться от текущего запроса? Должен ли он остановить приложение?
Решение проблемы
Резюмируя. Ошибка компиляции означает, что:
- ваш код выдал проверенное исключение или вызвал какой-либо метод или конструктор, который выдает проверенное исключение, и
- он не обработал исключение, перехватив его или объявив, как того требует язык Java.
Ваш процесс решения должен быть:
- Поймите, что означает исключение и почему оно могло быть выброшено. Прочитайте javadoc для исключения и метода, который генерирует исключение.
- На основании 1 прочтите свой код и примите решение о правильном способе обработки возможного исключения.
- На основе 2 внесите соответствующие изменения в свой код.
Пример: выбрасывание и перехват одним и тем же методом
Рассмотрим следующий пример из этого Q & A
public class Main {
static void t() throws IllegalAccessException {
try {
throw new IllegalAccessException("demo");
} catch (IllegalAccessException e){
System.out.println(e);
}
}
public static void main(String[] args){
t();
System.out.println("hello");
}
}
Если вы следовали тому, что мы говорили до сих пор, вы поймете, что t()
выдаст ошибку компиляции "незарегистрированное исключение". В данном случае ошибка заключается в том, что t
было объявлено как throws IllegalAccessException
. Фактически исключение не распространяется, потому что оно было перехвачено в методе, который его выдал.
Исправление в этом примере будет заключаться в удалении throws IllegalAccessException
.
Мини-урок здесь заключается в том, что throws IllegalAccessException
это метод, говорящий, что вызывающий должен ожидать распространения исключения. На самом деле это не означает, что оно будет распространяться. И обратная сторона заключается в том, что если вы не ожидаете распространения исключения (например, потому, что оно не было выброшено, или потому, что оно было перехвачено, а не повторно запущено), то в сигнатуре метода не должно быть указано, что оно выброшено!
Плохая практика с исключениями
Есть пара вещей, которых вам следует избегать:
Не перехватывайте
Exception
(илиThrowable
) как кратчайший путь для перехвата списка исключений. Если вы это сделаете, вы можете перехватить то, чего не ожидаете (например, непроверенныйNullPointerException
), а затем попытаться восстановить, когда не следует.Не объявляйте метод как
throws Exception
. Это заставляет вызываемый обрабатывать (потенциально) любое проверенное исключение ... что является кошмаром.Не подавляйте исключения. Например
try {
...
} catch (NullPointerException ex) {
// It never happens ... ignoring this
}Если вы сквошите исключения, вы можете значительно затруднить диагностику ошибок времени выполнения, которые их вызвали. Вы уничтожаете доказательства.
Примечание: просто вера в то, что исключение никогда не произойдет (согласно комментарию), не обязательно делает это фактом.
Крайний случай: статические инициализаторы
В некоторых ситуациях работа с проверяемыми исключениями является проблемой. Одним из конкретных случаев являются проверяемые исключения в static
инициализаторах. Например:
private static final FileInputStream input = new FileInputStream("foo.txt");
FileInputStream
Объявляется как throws FileNotFoundException
... что является проверяемым исключением. Но поскольку приведенное выше является объявлением поля, синтаксис языка Java не позволяет нам поместить объявление внутри try
... catch
. И нет подходящего (заключающего) метода или конструктора ... потому что этот код выполняется при инициализации класса.
Одним из решений является использование static
блока; например:
private static final FileInputStream input;
static {
FileInputStream temp = null;
try {
temp = new FileInputStream("foo.txt");
} catch (FileNotFoundException ex) {
// log the error rather than squashing it
}
input = temp; // Note that we need a single point of assignment to 'input'
}
(Существуют лучшие способы обработки приведенного выше сценария в практическом коде, но суть этого примера не в этом.)
Крайний случай: статические блоки
Как отмечалось выше, вы можете перехватывать исключения в статических блоках. Но мы не упомянули, что вы должны перехватывать проверенные исключения внутри блока. Для статического блока нет заключающего контекста, в котором могли бы быть перехвачены проверенные исключения.
Крайний случай: лямбды
Лямбда-выражение (обычно) не должно выдавать непроверенное исключение. Это не ограничение для лямбд само по себе. Скорее, это следствие интерфейса функции, который используется для аргумента, в котором вы предоставляете аргумент. Если функция не объявляет проверяемое исключение, лямбда-выражение не может его выдать. Например:
List<Path> paths = ...
try {
paths.forEach(p -> Files.delete(p));
} catch (IOException ex) {
// log it ...
}
Даже если кажется, что мы перехвачены IOException
, компилятор будет жаловаться, что:
- в лямбде есть неперехваченное исключение, И
catch
перехватывает исключение, которое никогда не выбрасывается!
Фактически, исключение должно быть перехвачено в самом лямбда-выражении:
List<Path> paths = ...
paths.forEach(p -> {
try {
Files.delete(p);
} catch (IOException ex) {
// log it ...
}
}
);
(Проницательный читатель заметит, что две версии ведут себя по-разному в случае, когда a delete
выдает исключение ...)
Крайний случай: два исключения с именем Xyzzy
Я видел случаи, когда метод имеет throws Xyzzy
предложение, а компилятор все еще жалуется, что это Xyzzy
должно быть "перехвачено или объявлено выброшенным". Например: Незарегистрированное исключение ...... должно быть перехвачено или объявлено выброшенным; несмотря на ключевое слово * thrown *.
Если вы окажетесь в такой ситуации, внимательно посмотрите на полные имена исключений. Если полные имена разные, то и исключения разные. (Верьте тому, что говорит компилятор, и постарайтесь это понять.)
Дополнительная информация
Учебное пособие Oracle по Java:
- Требование catch или specify ... также охватывает проверенные и непроверенные исключения.
- Перехват и обработка исключений
- Указание исключений, генерируемых методом