Приятной особенностью оператора Java 7 try-with-resources
и типа AutoCloseable
который был представлен для работы с этим оператором, является тот факт, что инструменты статического анализа кода могут обнаруживать утечки ресурсов. Например, Eclipse:
Если у вас есть вышеуказанная конфигурация и вы попытаетесь запустить следующую программу, вы получите три предупреждения:
1
2
3
4
5
6
7
8
9
|
public static void main(String[] args) throws Exception { Connection c = DriverManager.getConnection( "jdbc:h2:~/test" , "sa" , "" ); Statement s = c.createStatement(); ResultSet r = s.executeQuery( "SELECT 1 + 1" ); r.next(); System.out.println(r.getInt( 1 )); } |
Выход есть, тривиально
1
|
2 |
Предупреждения выпущены на всех c
, s
, r
. Быстрое исправление (не делайте этого!) Состоит в том, чтобы подавить предупреждение с помощью специфичного для Eclipse параметра SuppressWarnings
:
1
2
3
4
5
|
@SuppressWarnings ( "resource" ) public static void main(String[] args) throws Exception { ... } |
В конце концов, WeKnowWhatWeReDoing ™, и это всего лишь простой пример, верно?
Неправильно!
Правильный способ исправить это, даже для простых примеров (по крайней мере, после Java 7), состоит в использовании простого оператора try-with-resources.
01
02
03
04
05
06
07
08
09
10
11
|
public static void main(String[] args) throws Exception { try (Connection c = DriverManager.getConnection( "jdbc:h2:~/test" , "sa" , "" ); Statement s = c.createStatement(); ResultSet r = s.executeQuery( "SELECT 1 + 1" )) { r.next(); System.out.println(r.getInt( 1 )); } } |
На самом деле было бы замечательно, если бы Eclipse мог автоматически исправить это предупреждение и обернуть все отдельные операторы в оператор try-with-resources. Подтвердите этот запрос, пожалуйста!
Отлично, мы это знаем. Что за дело с Java 8?
В Java 8 контракт на AutoCloseable
изменился очень тонко (или прямо, в зависимости от вашей точки зрения).
Ресурс, который должен быть закрыт, когда он больше не нужен.
Обратите внимание на слово "must"
.
Объект, который может содержать ресурсы (например, дескрипторы файлов или сокетов), пока не будет закрыт. Метод close () объекта AutoCloseable вызывается автоматически при выходе из блока try-with-resources, для которого объект был объявлен в заголовке спецификации ресурса. Эта конструкция обеспечивает быстрое освобождение, избегая исключений исчерпания ресурсов и ошибок, которые могут возникнуть в противном случае.
API Примечание:
Для базового класса возможно и фактически является общим для реализации AutoCloseable, даже если не все его подклассы или экземпляры будут содержать высвобождаемые ресурсы. Для кода, который должен работать в полной общности, или когда известно, что экземпляр AutoCloseable требует освобождения ресурса, рекомендуется использовать конструкции try-with-resources. Однако при использовании таких средств, как Stream, которые поддерживают формы как на основе ввода-вывода, так и не на основе ввода-вывода, блоки пробного использования ресурсов обычно не нужны при использовании форм не на основе ввода-вывода.
Короче говоря, начиная с Java 8, AutoCloseable
— это скорее подсказка о том, что вы можете использовать ресурс, который необходимо закрыть, но это не всегда так.
Это похоже на контракт Iterable
, в котором не говорится, можете ли вы выполнить итерацию только один раз или несколько раз по Iterable
, но он накладывает контракт, необходимый для цикла foreach
.
Когда у нас есть «необязательно закрываемые» ресурсы?
Взять, к примеру, JOOQ . В отличие от JDBC, jOOQ Query ( который был сделан AutoCloseable
в jOOQ 3.7 ) может представлять или не представлять ресурс, в зависимости от того, как вы его выполняете. По умолчанию это не ресурс:
01
02
03
04
05
06
07
08
09
10
|
try (Connection c = DriverManager.getConnection( "jdbc:h2:~/test" , "sa" , "" )) { // No new resources created here: ResultQuery<Record> query = DSL.using(c).resultQuery( "SELECT 1 + 1" ); // Resources created and closed immediately System.out.println(query.fetch()); } |
Выход снова:
1
2
3
4
5
|
+----+ | 2 | +----+ | 2 | +----+ |
Но теперь у нас снова есть предупреждение Eclipse о переменной query
, в котором говорится, что существует ресурс, который необходимо закрыть, даже если при использовании jOOQ таким образом мы знаем, что это не так. Единственный ресурс в вышеприведенном коде — это Connection
JDBC, и оно правильно обрабатывается. JOOQ-внутренний PreparedStatement
и ResultSet
полностью обрабатываются и охотно закрываются jOOQ.
Тогда зачем в первую очередь реализовывать AutoCloseable?
jOOQ инвертирует поведение JDBC по умолчанию.
- В JDBC по умолчанию все выполняется лениво, и ресурсы должны быть явно закрыты.
- В jOOQ все делается по умолчанию с нетерпением, и, при желании, ресурсы могут быть сохранены в явном виде.
Например, следующий код будет держать открытыми PreparedStatement
и ResultSet
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
try (Connection c = DriverManager.getConnection( "jdbc:h2:~/test" , "sa" , "" ); // We "keep" the statement open in the ResultQuery ResultQuery<Record> query = DSL.using(c) .resultQuery( "SELECT 1 + 1" ) .keepStatement( true )) { // We keep the ResultSet open in the Cursor try (Cursor<Record> cursor = query.fetchLazy()) { System.out.println(cursor.fetchOne()); } } |
В этой версии у нас больше нет предупреждений в Eclipse, но вышеприведенная версия действительно является исключением при использовании jOOQ API.
То же самое относится и к Stream
API Java 8. Интересно, что Eclipse не выдает здесь никаких предупреждений:
1
2
|
Stream<Integer> stream = Arrays.asList( 1 , 2 , 3 ).stream(); stream.forEach(System.out::println); |
Вывод
Поначалу обнаружение утечки ресурсов кажется хорошей функцией IDE / компилятора. Но избежать ложных срабатываний сложно. В частности, поскольку Java 8 изменила контракты для AutoCloseable
, разработчикам разрешается реализовывать контракт AutoCloseable
для простого удобства, а не в качестве четкого индикатора присутствия ресурса, который ДОЛЖЕН быть закрыт.
Это делает очень трудным, если не невозможным, для IDE обнаружение утечек ресурсов сторонних API (не-JDK API), где эти контракты обычно не известны. Решение, как всегда с инструментами статического анализа кода, состоит в том, чтобы просто отключить обнаружение утечки потенциальных ресурсов:
Ссылка: | Незаметное автоматическое закрытие контракта между Java 7 и Java 8 от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ . |