try-with-resources и finally
Разбираем классический вопрос-ловушку про блок finally и современный способ Java закрывать ресурсы без утечек.
try-with-resources — конструкция Java, которая автоматически закрывает ресурсы (файлы, соединения с базой, сетевые сокеты) после выполнения блока кода, даже если внутри произошло исключение.
Что выведет этот код?
Классика собеседований — вопрос про порядок вывода при исключении внутри try/catch/finally:
public class Main {
public static void main(String[] args) {
try {
System.out.println("1: пробуем");
int result = 10 / 0;
System.out.println("2: этой строки не будет");
} catch (ArithmeticException e) {
System.out.println("3: поймали деление на ноль");
} finally {
System.out.println("4: finally выполняется всегда");
}
System.out.println("5: программа продолжается");
}
}Вывод:
1: пробуем
3: поймали деление на ноль
4: finally выполняется всегда
5: программа продолжаетсяСтрока «2» не выводится — как только на строке 10 / 0 выбрасывается ArithmeticException, выполнение сразу прыгает в блок catch, а всё, что осталось в try после места сбоя, пропускается. После catch обязательно отрабатывает finally — и только потом код идёт дальше.
Главный секрет finally
Блок finally выполняется практически всегда — независимо от того, было исключение или нет, поймали его или нет, и даже если внутри try или catch стоит return. Именно поэтому его используют для «уборки»: закрыть файл, освободить соединение с базой, разлочить ресурс.
Вот вопрос посложнее — что выведет этот код, если внутри try есть return?
public class Main {
public static int test() {
try {
System.out.println("Пробуем вернуть 1");
return 1;
} finally {
System.out.println("finally всё равно выполнится");
}
}
public static void main(String[] args) {
int value = test();
System.out.println("Получили: " + value);
}
}Вывод:
Пробуем вернуть 1
finally всё равно выполнится
Получили: 1Java подготавливает значение 1 к возврату, но перед тем как реально «выйти» из метода, обязательно выполняет finally. Есть только один способ, которым можно «обойти» finally: аварийное завершение всей JVM через System.exit(), либо крах самой JVM (например, отключение питания). В обычной логике программы finally сработает всегда.
Проблема ручного закрытия ресурсов
До Java 7 закрывать ресурсы приходилось вручную именно через finally, и код выглядел так:
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
FileReader reader = null;
try {
reader = new FileReader("data.txt");
System.out.println("Читаем файл");
} catch (IOException e) {
System.out.println("Ошибка чтения");
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
System.out.println("Не смогли закрыть файл");
}
}
}
}
}Обратите внимание: закрытие ресурса само может выбросить исключение, поэтому приходится оборачивать даже close() в ещё один try/catch. Такой код легко испортить — забыть проверку на null, забыть закрыть один из нескольких ресурсов, перепутать порядок закрытия.
Как это решает try-with-resources
Начиная с Java 7, появилась конструкция try-with-resources, которая делает всё это автоматически:
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (FileReader reader = new FileReader("data.txt")) {
System.out.println("Читаем файл");
} catch (IOException e) {
System.out.println("Ошибка чтения или закрытия");
}
}
}Ресурс объявляется прямо в скобках после try. Как только блок try завершается — успешно или с исключением — Java сама вызывает у ресурса метод close(), причём делает это раньше, чем передаст управление в catch или finally. Никакого ручного if (reader != null), никакого вложенного try/catch под закрытие.
Как это работает под капотом: AutoCloseable
«Магия» тут в интерфейсе AutoCloseable, у которого есть ровно один метод — close(). Любой класс, который реализует этот интерфейс, можно использовать в try-with-resources. FileReader, соединения с базой данных, сетевые сокеты — все они уже реализуют этот интерфейс за вас. Можно сделать и свой класс:
public class Connection implements AutoCloseable {
public void query() {
System.out.println("Выполняем запрос");
}
@Override
public void close() {
System.out.println("Соединение закрыто");
}
}
public class Main {
public static void main(String[] args) {
try (Connection conn = new Connection()) {
conn.query();
}
}
}Вывод:
Выполняем запрос
Соединение закрытоМожно объявить сразу несколько ресурсов через точку с запятой: try (Resource a = ...; Resource b = ...). Java закроет их в обратном порядке — тот, что открыт последним, закроется первым. Это логично: если второй ресурс зависит от первого, его нужно закрыть раньше.
Частые ошибки на собеседовании
- Считают, что
finallyможет не выполниться при обычном исключении. Не выполнится он только приSystem.exit()или крахе JVM. - Путаются в порядке при return внутри try и finally — если внутри finally тоже есть return, он «перебьёт» результат из try (это плохая практика, но её любят спрашивать отдельным вопросом).
- Не знают, что try-with-resources требует именно интерфейс
AutoCloseable, а не просто наличие методаclose()у класса. - Забывают, что ресурсы в try-with-resources закрываются в порядке, обратном объявлению.
Итоги-шпаргалка
finallyвыполняется практически всегда — даже при исключении и даже приreturnвнутриtry.try-with-resources(Java 7+) автоматически вызываетclose()у ресурса, убирая необходимость в ручномfinally.- Ресурс должен реализовывать интерфейс
AutoCloseable— тогда его можно объявить прямо в скобках послеtry. - При нескольких ресурсах закрытие идёт в обратном порядке: последний открытый — первый закрытый.