try-with-resources
Ресурс — это объект, который держит что-то внешнее: открытый файл, сетевое соединение, подключение к базе данных. Такие объекты нужно закрывать, иначе они остаются занятыми. Конструкция try-with-resources закрывает ресурсы автоматически — даже если внутри произошло исключение.
Чтение файла
Ресурсы объявляют в круглых скобках после try. Когда блок заканчивается, Java сама вызывает у них метод close(). В примере программа сначала создаёт файл, а потом читает его построчно через BufferedReader:
void main() throws IOException {
Files.writeString(Path.of("todo.txt"), "Купить хлеб\nПозвонить Олегу\n");
try (BufferedReader reader = Files.newBufferedReader(Path.of("todo.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
IO.println("- " + line);
}
}
}Результат:
- Купить хлеб
- Позвонить ОлегуПисать finally с reader.close() не нужно: файл закроется при любом исходе.
AutoCloseable
В скобках try может стоять любой объект, реализующий интерфейс AutoCloseable — с единственным методом close(). Его реализуют файлы, потоки ввода-вывода, соединения с базой данных. Можно реализовать и в своём классе, чтобы увидеть, когда происходит закрытие:
class Connection implements AutoCloseable {
Connection() {
IO.println("Соединение открыто");
}
void send(String message) {
IO.println("Отправлено: " + message);
}
@Override
public void close() {
IO.println("Соединение закрыто");
}
}
void main() {
try (Connection connection = new Connection()) {
connection.send("Привет");
}
IO.println("Готово");
}Результат:
Соединение открыто
Отправлено: Привет
Соединение закрыто
ГотовоЗакрытие при исключении
Главное преимущество — ресурс закрывается и при ошибке. Причём close() вызывается до того, как сработает catch:
class Connection implements AutoCloseable {
@Override
public void close() {
IO.println("Соединение закрыто");
}
}
void main() {
try (Connection connection = new Connection()) {
IO.println("Работаем");
throw new IllegalStateException("Сбой сети");
} catch (IllegalStateException e) {
IO.println("Ошибка: " + e.getMessage());
}
}Результат:
Работаем
Соединение закрыто
Ошибка: Сбой сетиНесколько ресурсов
Ресурсы перечисляют через точку с запятой. Закрываются они в обратном порядке: последний открытый — первым. Так, файл-источник и файл-копия закроются в правильной последовательности:
class Resource implements AutoCloseable {
private final String name;
Resource(String name) {
this.name = name;
IO.println("Открыт " + name);
}
@Override
public void close() {
IO.println("Закрыт " + name);
}
}
void main() {
try (Resource first = new Resource("A"); Resource second = new Resource("B")) {
IO.println("Работаем с A и B");
}
}Результат:
Открыт A
Открыт B
Работаем с A и B
Закрыт B
Закрыт AПодавленные исключения
Что, если исключение выбросил и блок try, и метод close()? Главным остаётся исключение из try, а второе не теряется: оно прикрепляется как подавленное (suppressed) и доступно через getSuppressed():
class Faulty implements AutoCloseable {
@Override
public void close() {
throw new IllegalStateException("ошибка при закрытии");
}
}
void main() {
try (Faulty resource = new Faulty()) {
throw new RuntimeException("ошибка в работе");
} catch (RuntimeException e) {
IO.println("Главная: " + e.getMessage());
IO.println("Подавленная: " + e.getSuppressed()[0].getMessage());
}
}Результат:
Главная: ошибка в работе
Подавленная: ошибка при закрытииЧастая ошибка
Переменная ресурса существует только внутри блока try. Попытка обратиться к ней после блока — ошибка компиляции, ведь к этому моменту ресурс уже закрыт:
void main() throws IOException {
Files.writeString(Path.of("data.txt"), "1\n2\n");
try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"))) {
IO.println(reader.readLine());
}
IO.println(reader.readLine());
}Результат:
Main.java:7: error: cannot find symbol
IO.println(reader.readLine());
^
symbol: variable reader
location: class Main
1 error
error: compilation failedВсю работу с ресурсом выполняйте внутри блока, а наружу передавайте уже готовый результат — строку, список, число.