Java 7的try-with-resources
语句和与该语句一起使用的AutoCloseable
类型的一个不错的功能是,静态代码分析工具可以检测到资源泄漏。 例如,Eclipse:
具有以上配置并尝试运行以下程序时,您将收到三个警告:
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));
}
输出是琐碎的
2
警告会在所有c
, s
和r
上发出。 一种快速的解决方案(不要这样做!)是使用Eclipse特定的SuppressWarnings
参数抑制警告:
@SuppressWarnings("resource")
public static void main(String[] args)
throws Exception {
...
}
毕竟,WeKnowWhatWeReDoing™,这只是一个简单的示例,对吧?
错误!
即使对于简单的示例(至少在Java 7之后),解决此问题的正确方法是使用轻松的try-with-resources语句。
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"
。
在关闭之前可以保存资源(例如文件或套接字句柄)的对象。 退出在资源规范头中已声明该对象的try-with-resources块时,将自动调用AutoCloseable对象的close()方法。 这种构造可确保及时释放,避免资源耗尽异常和可能发生的错误。
API注意:
即使并非所有子类或实例都拥有可释放的资源,基类也有可能并且实际上是常见的。 对于必须完全通用的代码,或者已知AutoCloseable实例需要释放资源的代码,建议使用try-with-resources构造。 但是,当使用诸如Stream的功能同时支持基于I / O和基于非I / O的形式时,使用非基于I / O的形式时通常不需要使用资源尝试模块。
简而言之,从Java 8开始, AutoCloseable
更具暗示性,表明您可能正在使用需要关闭的资源,但这并非一定如此。
这类似于Iterable
契约,后者没有说明您只能对Iterable
进行一次还是多次迭代,但是它强加了foreach
循环所需的契约。
我们什么时候拥有“可选的可关闭”资源?
以jOOQ为例。 与JDBC不同,jOOQ 查询 ( 在jOOQ 3.7中被设置为AutoCloseable
)可能表示资源,也可能不表示资源,这取决于您如何执行。 默认情况下,它不是资源:
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());
}
输出再次是:
+----+
| 2|
+----+
| 2|
+----+
但是现在,我们再次在query
变量上出现了Eclipse警告,说有一个资源需要关闭,即使通过这种方式使用jOOQ,我们知道事实并非如此。 上面的代码中唯一的资源是JDBC Connection
,并且已正确处理。 jOOQ内部的jOOQ PreparedStatement
和ResultSet
已完全处理,并急切地关闭了。
然后,为什么要首先实现AutoCloseable?
jOOQ与JDBC的默认行为相反。
- 在JDBC中,默认情况下所有工作都是延迟进行的,并且必须显式关闭资源。
- 在jOOQ中,默认情况下会急切地完成所有工作,并且可以有选择地使资源保持活动状态。
例如,以下代码将保持打开的PreparedStatement
和ResultSet
:
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时的例外。
Java 8的Stream
API也是如此。 有趣的是,Eclipse在这里不发出任何警告:
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
stream.forEach(System.out::println);
结论
首先,资源泄漏检测似乎是一个不错的IDE /编译器功能。 但是,避免误报很难。 具体而言,因为Java 8改变了合同AutoCloseable
,实现者被允许执行AutoCloseable
为一种方便的契约,而不是作为一种资源存在,必须关闭的清晰指示符。
这使IDE很难(甚至不是不可能)检测第三方合同(非JDK API)的资源泄漏,而这些合同通常并不为人所知。 与静态代码分析工具一样,该解决方案通常只是关闭潜在的资源泄漏检测:
翻译自: https://www.javacodegeeks.com/2015/12/subtle-autocloseable-contract-change-java-7-java-8.html