Які вимоги до ресурсів в try-with-resources?
Ресурс обов'язково має реалізовувати інтерфейс java.lang.AutoCloseable. Без цього його не можна використовувати у try-with-resources.
Junior Level
Головна вимога
Ресурс обов’язково має реалізовувати інтерфейс java.lang.AutoCloseable. Без цього його не можна використовувати у try-with-resources.
// Працює — FileInputStream реалізує AutoCloseable
try (FileInputStream fis = new FileInputStream("file.txt")) {
fis.read();
}
// Не скомпілюється — String не реалізує AutoCloseable
try (String s = "hello") { } // Помилка компіляції
Які класи реалізують AutoCloseable
- Усі
InputStream/OutputStream— файли, мережа - Усі
Reader/Writer— текст Connection,Statement,ResultSet— база данихScanner,Formatter— утиліти
Кілька ресурсів
try (FileInputStream fis = new FileInputStream("in.txt");
FileOutputStream fos = new FileOutputStream("out.txt")) {
// Обидва закриються автоматично
}
Java 9+ — зовнішні змінні
FileInputStream fis = createStream();
try (fis) { // fis повинен бути effectively final
fis.read();
}
Коли НЕ класти ресурс у try-with-resources
- Ресурс керується контейнером (Spring @Bean) — фреймворк сам закриє
- З’єднання з пулу (HikariCP) — пул керує lifecycle за вас
- Потрібно передати ресурс далі — return з метода, TWR закриє до повернення
Middle Level
AutoCloseable vs Closeable
AutoCloseable (Java 7, java.lang):
void close() throws Exception- Універсальний — для будь-яких ресурсів
Closeable (Java 5, java.io):
void close() throws IOException- Лише для I/O
- Успадковує
AutoCloseable
Для try-with-resources підходить будь-який з них.
Ризик витоку при ініціалізації
// БЕЗПЕЧНО — якщо B впаде, A закриється
try (A a = new A(); B b = new B(a)) { }
// НЕБЕЗПЕЧНО — якщо B впаде, A утече
try (B b = new B(new A())) { }
// A створено, але try не має на нього посилання
// Розбір витоку: new A() створив об'єкт. new B(a) кинув виняток.
// A створено, але змінна в TWR -- це B. У TWR немає посилання на A,
// тому close() для A не викличеться. Рішення:
// try (A a = new A(); B b = new B(a)) { ... }
Ідемпотентність close()
Метод close() повинен бути ідемпотентним — повторний виклик не повинен викидати виняток.
Ідемпотентність – властивість операції: повторний виклик дає той самий результат, що й перший. close() повинен бути ідемпотентним: перший виклик закриває ресурс, другий – нічого не робить (не кидає виняток).
public void close() {
if (closed.compareAndSet(false, true)) {
// реальне звільнення ресурсів
}
}
Вимоги до змінних (Java 9+)
Ресурс повинен бути final або effectively final. Компілятор створює локальну копію посилання — це гарантує, що якщо інший потік змінить зовнішню змінну, TWR все одно закриє оригінальний об’єкт.
effectively final = змінна не змінюється після ініціалізації, навіть якщо ключове слово final не написано. Компілятор бачить, що ви не змінюєте змінну, і дозволяє використовувати у TWR.
Senior Level
Under the Hood: компілятор створює копію
При використанні зовнішньої змінної у try(resource), компілятор створює локальну копію посилання. Це гарантує потокобезпечність закриття.
Кидання винятків з close()
Якщо close() кидає виняток:
- Якщо у
tryпомилок не було — виняток зclose()стане основним - Якщо у
tryвже був виняток — зclose()додасться уsuppressed
Interrupted close
У мережевих операціях close() може заблокуватися. Встановлюйте таймаути, інакше потік “зависне” на стадії закриття ресурсів.
Object Allocation та GC
try-with-resources — створення об’єктів. При відкритті/закритті тисяч ресурсів за секунду це створює тиск на GC (Young Generation). Використовуйте пули ресурсів (HikariCP для БД), які реалізують AutoCloseable, але замість закриття повертають об’єкт у пул.
Edge Cases
- Ресурс не відкрився — якщо конструктор кинув виняток, TWR не буде закривати (нема чого закривати)
- Часткова ініціалізація — якщо конструктор відкрив нативний ресурс, але впав до повернення — витік. Робіть конструктори легкими.
Діагностика
- Resource Leak Detection — IDE підсвічує
AutoCloseableкласи, не використані у TWR. Не ігноруйте. jstackanalysis — багато потоків уBLOCKED/WAITINGуclose()— перевірте дедлоки між ресурсами.
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Ресурс зобов’язаний реалізовувати
java.lang.AutoCloseable AutoCloseable(Java 7,java.lang) —close() throws Exception;Closeable(Java 5,java.io) —close() throws IOException- Метод
close()повинен бути ідемпотентним — повторний виклик не кидає виняток - Ресурси оголошуйте окремо:
try (A a = new A(); B b = new B(a))— інакше витік - Java 9+: зовнішні змінні повинні бути
effectively final - Компілятор створює локальну копію посилання — гарантує потокобезпечність закриття
- Якщо
close()кидає при наявній помилці уtry— додається уsuppressed - Пули ресурсів (HikariCP) реалізують AutoCloseable, але повертають об’єкт у пул, а не закривають
Часті уточнюючі запитання:
- У чому різниця AutoCloseable та Closeable? — AutoCloseable універсальніший (throws Exception), Closeable лише для I/O (throws IOException)
- Що таке ідемпотентність close()? — Перший виклик закриває ресурс, другий — нічого не робить
- Чому вкладене створення ресурсів небезпечне? —
new B(new A())— якщо B впаде, у TWR немає посилання на A, він утече - Що відбувається при масовому відкритті/закритті ресурсів? — Тиск на GC; використовуйте пули ресурсів
Червоні прапорці (НЕ говорити):
- “Close() може кидати виняток щоразу” — повинен бути ідемпотентним
- “Я не оголошую ресурси окремо, щоб коротше написати” — це викликає витоки
- “TWR закриває з’єднання з пулу” — пул керує lifecycle сам, повернення != закриття
Пов’язані теми:
- [[Що таке try-with-resources]]
- [[Що таке інтерфейс AutoCloseable]]
- [[В чому різниця між AutoCloseable та Closeable]]
- [[Що таке suppressed exceptions]]
- [[Які винятки потрібно обробляти обов’язково]]