Backend
October 11, 2023

Обработка ошибок и исключений в Java

Рассмотрим то, как можно обрабатывать ошибки и что для этого используется в Java. Эта тема может показаться скучной, но на самом деле это очень полезный навык, который пригодится тебе в будущем.

Что такое ошибки и почему их нужно обрабатывать

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

Если ошибку не обработать, программа просто «упадет» с ошибкой и не сможет продолжить работу. А нам нужно, чтобы даже при ошибках программа работала стабильно, не теряла данные пользователей и корректно сообщала о проблеме. Вот для этого и используется обработка ошибок.

Основные типы ошибок

Разные ошибки можно классифицировать по-разному.

Давай рассмотрим три основных типа:

  1. Синтаксические ошибки – когда код написан с нарушением syntax правил языка программирования. Например, не закрыта фигурная скобка или пропущена точка с запятой. Такие ошибки программа обычно выявляет сразу при компиляции кода.
  2. Логические ошибки – когда логика работы программы неверная. Например, вместо сложения делается вычитание. Программа скомпилируется, но будет работать неправильно.
  3. Ошибки времени выполнения – возникают во время работы программы. Например, деление на ноль, попытка открыть несуществующий файл, переполнение памяти и т.д.

Для обработки разных типов ошибок в Java используются исключения. Давай разберемся, как это работает.

Что такое исключения в Java

Исключение (exception) в Java – это объект, который содержит информацию об ошибке. Когда в программе возникает исключительная ситуация, создается объект-исключение и «выбрасывается» (throw).

Все исключения в Java унаследованы от базового класса Exception.

Есть также подклассы для разных типов ошибок:

  • RuntimeException – для ошибок времени выполнения
  • IOException – для ошибок ввода-вывода
  • NullPointerException – для ошибок с нулевым указателем
  • И т.д.

Блок try-catch

В Java для обработки исключений используется конструкция try-catch.

Вот простой пример:

try {
  // код, который может выбросить исключение

} catch (Exception e) {
  // код для обработки исключения
}

В блоке try пишется код, который потенциально может выбросить исключение. Если исключение произошло, управление передается в блок catch. Там мы можем обработать ошибку: вывести сообщение, записать в лог и т.д.

После блока catch программа продолжит выполнение дальше, как ни в чем не бывало. Таким образом мы «перехватили» ошибку и не дали программе упасть.

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

Мы можем обработать его так:

try {
  File file = new File("test.txt");
  FileReader fr = new FileReader(file); 

} catch (FileNotFoundException e) {
  System.out.println("Файл не найден: " + e);
} 
// далее программа продолжит работу

Если файл не найден, будет выведено сообщение об ошибке, а программа не «упадет».

Блок finally

После блоков try-catch можно добавить еще блок finally:

try {
   // код 
 
} catch (Exception e) {
  // обработка ошибки

} finally {
  // этот код выполнится в любом случае 
}

Блок finally выполняется всегда, даже если в блоке try не было исключений. Это удобно для закрытия файлов, соединений с базами данных и других «уборок» после работы кода.

Например:

FileReader fr = null;

try {
  fr = new FileReader("file.txt");
  // читаем файл
  
} catch (IOException e) {
  // обработка ошибки
  
} finally {
  if (fr != null) {
    fr.close(); // закрываем поток чтения файла
  }
}

Обработка нескольких типов исключений

Мы можем обрабатывать в блоке catch не только общее исключение Exception, но и конкретные классы.

Например:

try {
  // код
  
} catch (FileNotFoundException e) {
  // файл не найден
  
} catch (IOException e) {
  // ошибка ввода-вывода 
  
} catch (Exception e) {
  // другие ошибки
}

Сначала пишутся блоки для конкретных исключений, а в конце – общий блок catch. Так мы можем различать разные типы ошибок и обрабатывать их по-разному.

Создание собственных исключений

В Java можно создавать собственные классы исключений, унаследовав их от Exception или других подклассов.

Например:

public class MyException extends Exception {
  public MyException(String message) {
    super(message);
  }
}

А затем уже выбрасывать эти исключения в коде через оператор throw:

if (amount < 0) {
  throw new MyException("Сумма не может быть отрицательной"); 
}

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

Использование throws и throw

  • Метод может объявлять, какие исключения он выбрасывает, через ключевое слово throws:
void readFile(String file) throws IOException {
  // код, который может выбросить IOException
}

Теперь вызывающий метод должен будет обработать это исключение.

  • Оператор throw выбрасывает исключение из метода:
throw new IOException("Ошибка чтения файла");

После этого выполнение метода прекращается и исключение передается выше по стеку вызовов.

Рекомендации

Простые советы:

  • Не игнорируй исключения просто так. Лучше хотя бы вывести сообщение об ошибке.
  • Не используй обработку исключений для управления логикой программы. Для этого есть операторы if/else.
  • Старайся писать понятные сообщения об ошибках для других разработчиков.
  • Используй finally для освобождения ресурсов.
  • Не бойся создавать собственные типы исключений, если это упростит код.