1. Исключения как часть API
Почему исключения — это часть "контракта" метода?
Когда вы пишете метод, вы определяете не только параметры и возвращаемое значение, но и какие исключения он может выбросить. Это часть «контракта» между вашим методом и теми, кто его будет использовать. Если метод может выбросить исключение, об этом должны знать все, кто его вызывает — чтобы правильно обработать ошибку (или хотя бы не удивляться, когда программа внезапно завершится с ошибкой).
Пример:
public void readFile(String filename) throws IOException {
// ... чтение файла
}
Здесь явно указано, что метод может выбросить IOException. Это сигнал для пользователя метода: «Будь готов обработать ошибку чтения файла!»
Документирование исключений: аннотация @throws в Javadoc
Чтобы другие разработчики понимали, какие исключения может выбросить ваш метод, используйте аннотацию @throws (или @exception) в Javadoc.
Пример:
/**
* Читает содержимое файла.
*
* @param filename имя файла
* @return содержимое файла в виде строки
* @throws IOException если произошла ошибка чтения файла
*/
public String readFile(String filename) throws IOException {
// ...
}
Зачем это нужно?
- Помогает другим разработчикам понять, какие ошибки нужно обрабатывать.
- IDE и генераторы документации (например, Javadoc) показывают эти исключения прямо в подсказках.
- Повышает надёжность и предсказуемость кода.
Checked и unchecked исключения в API
Checked-исключения (наследники Exception, но не RuntimeException) — часть контракта метода. Их нужно либо обработать, либо явно пробросить дальше (throws).
Unchecked-исключения (RuntimeException и наследники) — обычно сигнализируют о программных ошибках (например, NullPointerException, IllegalArgumentException). Их не обязательно указывать в сигнатуре, но если ваш метод может выбросить такую ошибку (например, при некорректных аргументах), это тоже стоит описывать в Javadoc.
Пример:
/**
* Делит a на b.
* @param a делимое
* @param b делитель
* @return результат деления
* @throws IllegalArgumentException если b == 0
*/
public int divide(int a, int b) {
if (b == 0) throw new IllegalArgumentException("Делитель не может быть нулём");
return a / b;
}
Исключения и проектирование API
- Думайте о пользователе вашего метода: какие ошибки он может и должен обработать? Какие — это баги, а какие — «рабочие» ситуации?
- Не злоупотребляйте checked-исключениями: если ошибка — это баг (например, неверный аргумент), лучше выбрасывать unchecked-исключение.
- Документируйте все исключения, которые могут быть выброшены «наверх».
2. Конструкция try-with-resources
Проблема: как безопасно закрывать ресурсы?
Во многих задачах приходится работать с ресурсами, которые нужно обязательно закрывать после использования: файлы, сетевые соединения, базы данных и т.д. Если забыть закрыть ресурс — можно получить утечку памяти, блокировку файла или другие неприятности.
Раньше:
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
// ...
} catch (IOException e) {
// обработка ошибки
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// обработка ошибки при закрытии
}
}
}
Много кода, легко ошибиться, можно забыть закрыть ресурс.
Решение: try-with-resources
С Java 7 появилась конструкция try-with-resources, которая автоматически закрывает все ресурсы, даже если внутри блока возникло исключение.
Синтаксис:
try (ResourceType resource = new ResourceType(...)) {
// работа с ресурсом
} catch (ExceptionType e) {
// обработка ошибки
}
Пример:
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Ошибка при чтении файла: " + e.getMessage());
}
// reader.close() вызовется автоматически!
Как это работает?
- В скобках после try объявляются ресурсы, которые нужно закрыть.
- После выхода из блока try (даже если возникло исключение!) для каждого ресурса вызывается метод close().
- Это работает только для ресурсов, реализующих интерфейс AutoCloseable (или его предка Closeable).
Интерфейс AutoCloseable:
public interface AutoCloseable {
void close() throws Exception;
}
Все стандартные ресурсы Java (файлы, потоки, соединения с БД) реализуют этот интерфейс.
Можно объявлять несколько ресурсов
try (
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
Оба ресурса будут закрыты автоматически, даже если возникнет исключение.
Преимущества try-with-resources
- Безопасность: ресурсы всегда закрываются, даже при ошибках.
- Краткость: меньше кода, меньше шансов ошибиться.
- Читаемость: сразу видно, какие ресурсы используются и когда они закрываются.
3. Практика: пишем безопасный код с try-with-resources
Пример: чтение файла
public static void printFirstLine(String filename) {
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line = reader.readLine();
System.out.println("Первая строка: " + line);
} catch (IOException e) {
System.out.println("Ошибка: " + e.getMessage());
}
}
Пример: запись в файл
public static void writeToFile(String filename, String text) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
writer.write(text);
} catch (IOException e) {
System.out.println("Ошибка при записи: " + e.getMessage());
}
}
Пример: свой ресурс
Если вы пишете свой класс, который нужно закрывать, просто реализуйте AutoCloseable:
public class MyResource implements AutoCloseable {
@Override
public void close() {
System.out.println("Ресурс закрыт!");
}
}
Теперь его можно использовать в try-with-resources:
try (MyResource res = new MyResource()) {
// работа с ресурсом
}
4. Типичные ошибки и best practices
Ошибка №1: забыли закрыть ресурс (без try-with-resources).
Если не использовать try-with-resources, легко забыть закрыть файл или поток — это приводит к утечкам ресурсов.
Ошибка №2: попытка использовать try-with-resources с объектом, который не реализует AutoCloseable.
Если ваш класс не реализует этот интерфейс, компилятор не даст использовать его в try-with-resources.
Ошибка №3: не документируете исключения в API.
Если ваш метод может выбросить исключение — обязательно указывайте это в сигнатуре (throws) и в Javadoc (@throws). Это поможет другим правильно использовать ваш код.
Ошибка №4: ловите Exception вместо конкретных ошибок.
Лучше ловить только те исключения, которые реально ожидаете и умеете обрабатывать.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ