Java AutoClosable
# Java AutoClosable
就像编程中写括号一样,每次写括号都是成对出现,以保证括号匹配。资源的申请和释放也应该如此,必须保证申请的资源在使用完毕后能够正确得到释放。
# try-with-resources
自 Java 7 开始,JDK 引入了一个接口 AutoClosable,我们可以利用它,借助于 try-with-resources 的写法来保证资源的释放。AutoClosable 接口很简单,只有一个方法,它代码如下:
public interface AutoCloseable {
/* Closes this resource, relinquishing any underlying resources.
* This method is invoked automatically on objects managed by the
* try-with-resources statement.
*
* @throws Exception if this resource cannot be closed
*/
void close() throws Exception;
}
比如,我们设计一个 Java 的类 CustomClass,它里面的 res 是我们要释放的资源。此 res 在构造函数中申请资源,并且将在接口的 close()
方法中完成资源的释放。其中的方法 getRes()
仅用于外部检查资源的状态,它并不重要。此 Java 类的代码如下:
public class CustomClass implements AutoCloseable {
// the simulated resource need to be released.
private Object res;
public CustomClass() {
// initialize the resource in constructor.
res = new String("A simulated string resource");
}
@Override
public void close() throws Exception {
System.out.println("CustomClass.close() get called.");
// release the resource, and set it to null.
res = null;
}
public Object getRes() {
return res;
}
}
用传统的 try-finally 方式来保证资源得到释放的代码如下:
CustomClass cc1 = null;
try {
cc1 = new CustomClass();
//使用此类...
} finally {
if (cc1 != null) {
try {
cc1.close();
} catch (Exception e) {
// this error from close() can be ignored.
//e.printStackTrace();
}
}
}
在 finally 中我们将确保调用 cc1.close()
以释放资源。上述单元测试的代码运行结果显示,代码能够如预期的正常工作,即便在 try 代码块中发生异常也能保证资源得到释放。只是这种写法显得很啰嗦,如果有多个资源的情况下,更是如此。
如果用 try-with-resource 的写法,会简单明了一些:
try (CustomClass cc1= new CustomClass()) {
//使用此类...
} catch (Exception e) {
// this error from close() can be ignored.
//e.printStackTrace();
}
在 try 的括号中申请资源,此资源在 try 代码块中使用,离开代码块将自动调用 close()
方法,从而保证资源得到释放。如果有多个资源,写多条语句在 try 的括号中即可。
相比之前的 try-finally 写法,try-with-resources 是较为推荐的写法,建议使用。
# 在 try 中返回
值得提及的是,即便在 try-finally 的 try 代码块中发生函数返回(return),资源也能得到释放。
我们设计一个单元测试,用传统的 try-finally 方式来编写,代码如下:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("junit5")
public class CustomClassReturnTryFinallyTest {
private static void simulateRuntimeException() {
throw new RuntimeException("A designed runtime exception.");
}
/**
* An example method to return in try block.
*
* @param simulateException Whether to throw exception before return.
* @return
*/
private static CustomClass returnInTryBlock(boolean simulateException) {
CustomClass cc1 = null;
try {
cc1 = new CustomClass();
// intentionally throw an exception here.
if (simulateException) {
simulateRuntimeException();
}
// Guess whether cc1 is closed or not?
return cc1;
} finally {
if (cc1 != null) {
try {
cc1.close();
} catch (Exception e) {
// do nothing as this error from close() can be ignored.
//e.printStackTrace();
}
}
}
}
}
在 returnInTryBlock()
方法中,不管是否发生 Exception (在代码中模拟抛出和不抛出 RuntimeException 两种情况),在 try 代码块中,函数调用返回(return)前,我们都可以看到资源被正确地释放了。
如果用 try-with-resources 的写法,代码如下:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("junit5")
public class CustomClassReturnTryWithResourceTest {
private static void simulateRuntimeException() {
throw new RuntimeException("A designed runtime exception.");
}
/**
* An example method to return in try block.
*
* @param simulateException Whether to throw exception before return.
* @return
*/
private static CustomClass returnInTryBlock(boolean simulateException) {
try (CustomClass cc1 = new CustomClass();) {
// intentionally throw an exception here.
if (simulateException) {
simulateRuntimeException();
}
// Guess whether cc1 is closed or not?
return cc1;
} catch (Exception e) {
// do nothing as this error from close() can be ignored.
//e.printStackTrace();
}
return null;
}
}
我们可以观察到同样的行为,即在 returnInTryBlock()
方法中,不管是否发生 Exception (在代码中模拟抛出和不抛出 RuntimeException 两种情况),在函数调用返回(return)前,我们都可以看到资源被正确地释放了。
# 小结
要保证资源在使用完毕后得到释放,可以用传统的 try-finally 方式或 try-with-resources 编写代码,后者需要实现 在 Java 7 引入的 AutoClosable 接口,编写的代码更简洁。在 try 代码块中,如果发生异常,或是有函数调用返回(return),也能保证资源得到正确释放。
# 参考链接
关于 AutoClosable 和 try-with-resources 的具体技术细节可参考 Oracle 官方的文档:
另外,StackOverflow 上的一篇问答帖子也值得参考: