(Когда) следует попробовать с вложением автоматически закрываемого конструктора?

#java #try-catch

#java #попробуйте-поймайте

Вопрос:

В документации Oracle Java для новой на тот момент попытки с ресурсами показаны все примеры, в AutoCloseable которых рассматриваемый try(...) конструктор создается в:

 try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    ...
}
  

Это кажется безопасным в том смысле, что даже если конструктор (в отличие от некоторого последующего события) уже получает ресурсы, которые должны быть освобождены, они будут освобождены, если мы столкнемся с исключением.

Однако, как, например, https://www.baeldung.com/java-try-with-resources указывает, что, начиная с Java 9, мы можем эффективно использовать конечные ресурсы, инициализированные перед try(...) :

 FileOutputStream fos = new FileOutputStream("output.txt");
try (fos) { 
    // do stuff
}
  

В аналогичном, предшествующем Java-9 ключе, я видел:

 CustomStreamLikeThing connection = new CustomStreamLikeThing("db_like_thing");
// ... do some stuff that may throw a RuntimeException
try (CustomStreamLikeThing connection2=connection) { 
    // do some more stuff
}
  

Что кажется законным, но в то же время несколько ослабляет концепцию: если CustomStreamLikeThing приобретает закрываемые ресурсы, они могут не быть освобождены.

Существуют ли рекомендации о том, когда конструктор AutoCloseable должен быть заключен в try(...) , или это дело вкуса (как в «вложить все те вещи, которые, я думаю, могут вызвать исключение»)?

Комментарии:

1. Является ли переменная полезной для вас вне блока try-with-resources? Если нет, то вам, вероятно, следует объявить его внутри.

Ответ №1:

Вы должны всегда использовать попытку с ресурсами, если можете.

Когда вы выделяете ресурс, вы должны убедиться, что ресурс всегда освобождается, когда вы закончите с ним. Это «всегда» обычно означает, что вы должны использовать try-finally , чтобы любая ошибка, возникающая после выделения ресурса, не препятствовала освобождению ресурса.

Это означает, что если вы не используете try-with-resources, вам нужно использовать try-finally , так почему я сказал всегда использовать try-with-resources?

Потому что вызов close() тоже может завершиться неудачей, и try-with-resources обрабатывает это за вас с помощью функции, добавленной при добавлении try-with-resources, называемой подавленными исключениями.

Вы поймете важность этого, если вы когда-либо проклинали stacktrace неудачного close() вызова, который заменил исключение, вызванное кодом, который должен был находиться в блоке try-with-resources, так что теперь вы не знаете, что на самом деле вызвало проблему.

Подавленные исключения — это часто упускаемая из виду функция, но она значительно экономит время, когда close метод генерирует каскадное исключение. Только для этого всегда используйте try-with-resources.

Побочное преимущество: Try-with-resources содержит меньше кода, чем using try-finally , и вы должны определенно использовать один из этих двух при обработке ресурсов.

Ответ №2:

Существуют ли рекомендации о том, когда конструктор автоматически закрываемого должен быть заключен в try(...) , или это дело вкуса (как в «вложить все те вещи, которые, я думаю, могут вызвать исключение»)?

Основное правило в кодировании таково: вы делаете все специально. Для этого требуется, чтобы вы понимали, что вы делаете, и что делает каждая «вещь» в вашем коде.

Значение: если у вас есть что-то, что нужно «закрыть», то правило здравого смысла таково: используйте try с ресурсами.

Но вы спрашиваете конкретно о тонком аспекте «создания объекта». Должно ли это входить в try() , или это можно сделать раньше?!

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

  • Вы разговариваете со своей командой и соглашаетесь с вашим «предпочтительным» стилем, а затем вся ваша команда следует этому. Другими словами: выберите стиль, который вы предпочитаете, а затем придерживайтесь этого: будьте последовательны!
  • Вы смотрите на свои требования. Ваш try(fos) вариант работает с java9. Для людей, которым не нужно исправлять старые продукты, это нормально. Но для людей, которым регулярно приходится вносить «одно и то же изменение» в разные версии одного и того же продукта… эти люди могут предпочесть исходный код, который можно просто объединить в среду java8.

Ответ №3:

При наличии конструктора непосредственно в инструкции try-with-resources, подобной этой:

 try (MyCloseable c = new MyCloseable()) {
    // do stuff with c
} catch (IOException e) {
    // handle exception
}
  

тогда блок catch будет вызван, когда любое из следующих действий вызовет исключение:

  • конструктор
  • тело попытки
  • close() -метод.

Таким образом, у вас не может быть блока catch для каждого особого случая, когда, например, все бросают IOException .

В то время как с помощью кода, подобного этому, вы можете немного больше контролировать обработку исключений:

 MyCloseable c;
try {
    c = new MyCloseable();
} catch (IOException e) {
    // handle constructor exception
}
try (c) {
    // do stuff with c
} catch (IOException e) {
    // handle exception from close() or try-body
}
  

Но опять же, у вас все еще есть один блок catch для try-body и метода close в приведенном выше примере, и делать что-то подобное не рекомендуется:

 // initialize c like in the second example
try (c) {
   try {
       // do stuff with c
   } catch (IOException e) {
       // handle try-body exception
   }
} catch (IOException e) {
   // handle close() exception
}
  

Ответ №4:

Предварительная java 9 — это просто хак, позволяющий автоматически закрывать работу с базовым объектом и закрывать и освобождать ресурсы.

В Java 9 это больше встроено в сам язык, чтобы разрешить закрытие ресурсов, инициализированных вне блока try with.

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