Java AutoClosable


2020-12-15 java

# 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 上的一篇问答帖子也值得参考:

Last Updated: 12/23/2024, 4:08:19 AM