理解try-with-resources语句及示例

邹正阳
2023-12-01

原文链接

http://javapapers.com/core-java/try-with-resources/

概述

Java 7 gave us try-with-resources, a nice feature on exception handling. This tutorial is part 3 of the exception handling series. Part I is about basics of exception handling. Part II is about exception hierarchy, stack traces, handling exception and best practices. Now in this part three of exception handling tutorial, we will see about try-with-resources (ARM) introduced in Java version 7.
  Java7提供的try-with-resources语句,是异常处理的一大利器。本指南是异常处理系列的第三部分,第一部分讲了异常处理的相关基础知识,第二部分有关异常的层级、堆栈跟踪、异常处理与最优做法。第三部分将讨论Java7引入的try-with-resources语句(资源的自动化管理)。


Close the resources in finally block is a well know rule for all java developers. As part of exception handling mechanism, we should always ensure to close and release the resources that is used inside the block. In a block of code which is enclosed in a try-catch block, if an exception is thrown the normal flow is altered.
  对所有java开发人员而言,在finally代码块中关闭资源是再熟悉不过的处理方式了。异常处理的一部分工作,就是确保代码块中的各类资源被关闭和有效释放。一旦try-catch语句块中的代码抛出了异常,之前的工作流程就会发生改变。

try {
	//code statements(正常代码语句)
	//exception thrown here(抛出异常的语句)
	//lines not reached if exception thrown(异常抛出后无法执行的代码部分)
} catch (Exception e) {
	//lines reached only when exception is thrown(抛出异常后才会执行的代码部分)
} finally {
	//always executed irrespective of an exception thrown or not
	//(无论是否抛出异常都会执行的代码部分)
}

Look at the above code block. The best place to close and release all the resources is finally block. So what is the resource we are talking about here? Resources means database connection, file connections, etc.
  显然,上述代码中,关闭并释放各类资源的最佳位置,就是finally部分。这里提到的资源,就是像数据库连接、文件连接等等这样的具体实例或对象。

Poor Structure

糟糕的结构

As stated above, we are going to close the resources in the finally block. Look at the following code, how ugly it looks like.
  如前所述,要在finally语句中关闭资源。来看看下面的代码有多么难看:
...
InputStream is = null;
try {
	is = new FileInputStream("test");
	is.read();
	...
} catch (Exception e) {
	...
} finally {
	try {
		is.close();
	} catch (IOException e) {
		e.printStackTrace();
		...
	}
}

Explicitly the programmer has to close the resource and need to surround it with a try-catch block and all inside the finally. This is a standard repeated code in java projects.
  程序员不得不显式地关闭某个资源,并且还要在finally中反复使用try-catch语句来处理这条关闭语句,这是java程序中典型的冗余代码。

We just need to put a try-catch and we don't do anything to handle it there. Apache's IOUtils introduced methods to use inside finally block such as above.
  而我们只是希望用一个try-catch语句解决所有问题。阿帕奇的IOUitils辅助工具类提供了诸如此类的方法应用于finally语句中(然并卵)。

org.apache.commons.io.IOUtils has methods to suppress the exception thrown inside finally block when the close method is executed.
public static void closeQuietly(InputStream input)
  org.apache.commons.io.IOUtils工具类中提供了一些方法,可以抑制在finally中执行close方法时抛出的异常。其语法为:
public static void closeQuietly(InputStream input)

Automatic Resource Management (ARM)

资源的自动化管理

In Java 7, we got a nice feature to manage these resources automatically. Manage is really a hype word here, all it does is close the resources. Automatic resource management, helps to close the resources automatically.
  Java7为我们提供了一个非常强悍的功能来自动关闭这些资源。“管理”一词在这里燃值颇高,其实它的工作就是关闭资源。资源自动化管理帮助我们自动关闭这些资源。

Resource instantiation should be done within try(). A parenthesis () is introduced after try statement and the resource instantiation should happen within that paranthesis as below,
  资源的初始化工作是在try()中完成的。try后面引入了一对小括号(),用于在其中完成资源的初始化,示例如下:
try (InputStream is = new FileInputStream("test")) {
	is.read();
	...
} catch(Exception e) {
	...
} finally {
	//no need to add code to close InputStream, its close method will be internally called
	//(无需添加关闭InputStream输入流的代码,其close()方法会自行调用)
}

try-with-resources and AutoCloseable

try-with-resources语句和AutoCloseable接口

  • All the classes cannot be used in try-with-resources. AutoCloseable is an interface used as a contract to implement try-with-resources. Classes that implements AutoCloseable must be used as a resource in try-with-resources, else we will get compilation error.
      并非所有的类都能用try-with-resources处理。实现AutoCloseable接口是使用try-with-resources语句的前提。在try-with-resources语句中,只有实现了AutoCloseable接口的类才会被视为资源进行相关处理,否则会出现编译错误。

  • close is the only method in AutoCloseable and it gets automatically invoked at runtime.
    close方法是AutoCloseable接口的唯一方法,该方法在程序运行时会被自动调用。

  • Multiple classes can be declared within the same try as “try (Lion lion = new Lion(); Tiger tiger = new Tiger()) {…”
    在同一个try语句中可以声明多个资源类,例如:“try (Lion lion = new Lion(); Tiger tiger = new Tiger()) {…”

  • During initialization of multiple resources in ‘try’, if there is any issue then immediately in the reverse order those resources that are already initialized will be closed.
    try中的多个资源在初始化过程中,一旦发生异常,那些已经完成初始化的资源,将按照与其创建时相反的顺序依次关闭。

  • When multiple classes are used in ‘try’, then the close method is called in the reverse order. To understand this, check the below example.
    若try中用到了多个资源类,则close方法会按照与其声明时相反的顺序依次调用。后面的示例代码对此进行了阐释。

  • try-with-resources, can have catch and finally. They work as usual and no change in it.
    try-with-resources也可以与catch和finally关键字连用,功能与之前一样。

  • In try-with-resources on exception, before going to catch the close statements will be executed.
    如果在try-with-resources语句中遇到了异常,close关闭语句会先于catch语句执行。

  • While implementing AutoCloseable, best practice is to throw specific exception and not the highest level ‘Exception’ itself. This needs to highlighted because the signature of ‘close’ in AutoCloseable throws Exception.
    实现AutoCloseable接口时,最佳做法是抛出一个具体的异常,而不是抛出最上级的Exception自身。这一点需要引起重视,因为AutoCloseable接口的close方法签名抛出的就是Exception异常。

  • On implementation, AutoCloseable.close should not throw InterrupedException, because at runtime if this exception is suppressed it will cause issues in thread handling.
    实现AutoCloseable接口时,AutoCloseable.close()方法不宜抛出InterrupedException被中断异常,因为程序运行时一旦该异常被抑制,将会引发指针处理方面的问题。

  • By specification this close method is not required to be idempotent. But still, it is best practice to implement it as idempotent. That is, even if the close method is invoked multiple times, it should act as same.
    按照规范,这里的close方法不能是幂等的,但最佳做法仍然是将其实现为幂等的。也就是说,即便close方法被多次调用,其结果都是一致的。

  • Closeable is an interface that extends AutoCloseable and its close method must be idempotent.
    Closeable接口继承了AutoCloseable接口,其close方法是幂等的。

  • Resource declared in try gets instantiated just before the start of the try-block.
    try中声明的资源,会在try代码块开始执行之前完成资源的实例化。

  • Resource is implicitly declared as final.
    try-with-resources中的资源会被隐式地声明为final。
Custom Implementation of AutoCloseable

AutoCloseable接口的用户自定义实现

Let us create a custom class that implement AutoCloseable. It is not rocket science, there is only one method named ‘close’ in the interface and implementing that is sufficient.
  接下来创建一个自定义类,来实现AutoCloseable接口。这并不难,因为接口中只有一个名为close的方法,实现它就搞定了。

创建狮子类Lion.java:
package com.javapapers.exceptionhandling;

public class Lion implements AutoCloseable {
	public Lion() {
		System.out.println("LION is OPEN in the wild.");
	};

	public void hunt() throws Exception {
		throw new Exception("DeerNotFound says Lion!");
	}

	public void close() throws Exception {
		System.out.println("LION is CLOSED in the cage.");
		throw new Exception("Unable to close the cage!");
	}
}


创建老虎类Tiger.java:
package com.javapapers.exceptionhandling;

public class Tiger implements AutoCloseable {
	public Tiger() {
		System.out.println("TIGER is OPEN in the wild.");
	};

	public void hunt() throws Exception {
		throw new Exception("DeerNotFound says Tiger!");
	}

	public void close() throws Exception {
		System.out.println("TIGER is CLOSED in the cage.");
	}
}

创建测试类TryWithResourcesExample.java:

package com.javapapers.exceptionhandling;

public class TryWithResourcesExample {

	public static void main(String[] args) {
		try (Lion lion = new Lion(); Tiger tiger = new Tiger()) {

			lion.hunt();
			tiger.hunt();

		} catch (Exception e) {
			System.out.println(e);
		} finally {
			System.out.println("Finally.");
		}
	}
}

Output for above example:(运行结果)

LION is OPEN in the wild.
TIGER is OPEN in the wild.
TIGER is CLOSED in the cage.
LION is CLOSED in the cage.
java.lang.Exception: DeerNotFound says Lion!
Finally.

Look at the sequence of output listed above to understand the flow of control and the order in which the close method is invoked internally.
  可以对照上面列出的运行结果,来理解程序的控制流变化情况,以及close方法在try-with-resources语句内生调用过程中的顺序。

Throwable.getSuppressed Exceptions

关于Throwable.getSuppressed()异常

Read the Lion and Tiger example line by line. There is an important point to understand is hidden in it. Now go back and read Lion.java
  逐句查看狮子和老虎的示例程序,里面隐藏了一个的重要知识点。回到狮子类Lion.java:
public void close() throws Exception {
	System.out.println("LION is CLOSED in the cage.");
	throw new Exception("Unable to close the cage!");
}
Lion’s close throws an exception, but where is it in the output? It got suppressed. That is the concept of try-with-resources.
  狮子的close方法抛出了一个异常,为什么运行结果中没有显示呢?因为该异常的抛出被抑制了,这是在try-with-resources语句中特有的情况。

If an exception is thrown in the try block, then the control will be transferred to catch. In between the jump to catch block the close() will be internally invoked for the registered resources.
  如果try代码块中抛出了异常,则控制权转移到catch部分。在控制权跳转过程中,该资源会自动调用close()方法。

Inside the close(), there is a possibility for an exception while closing the resource. If an exception is thrown (as given in Lion example), then it will be suppressed and the exception that is thrown in the try block (before calling close), will be exposed.
  在用close()方法关闭该资源时,可能会抛出一个异常,如示例中的Lion所示,该关闭异常将被抑制;而try代码块中抛出的异常(try{}中调用close方法前被抛出的异常),会被try-with-resources语句真正抛出。

So how do we know about the suppressed exception? Here you go, use the Throwable.getSuppressed get the exceptions that are suppressed in try-with-resources.
  那我们如何查看这些被抑制的异常呢?答案是用Throwable.getSuppressed()方法来获取。


If there are multiple resources to be closed and an exception is thrown during closing of one exception, irrespective of that all the resources will be closed.
  如果有多个资源需要关闭,且在关闭其中一个资源时抛出了异常,则该异常将不会影响其他资源正常关闭。

When try-with-resources compiled to bytecode…

当try-with-resources被编译为字节码...


As given in Java Language Specification,
Java语言规范如下:
try (VariableModifiersopt R Identifier = Expression ...)
    Block

following is the equivalent code of above, when converted to without try-with-resources,
  当转换成未使用try-with-resources语句的字节码时,上述规范的等效代码示例如下:
{
    final VariableModifiers_minus_final R Identifier = Expression;
    Throwable #primaryExc = null;


    try ResourceSpecification_tail
        Block
    catch (Throwable #t) {
        #primaryExc = #t;
        throw #t;
    } finally {
        if (Identifier != null) {
            if (#primaryExc != null) {
                try {
                    Identifier.close();
                } catch (Throwable #suppressedExc) {
                    #primaryExc.addSuppressed(#suppressedExc);
                }
            } else {
                Identifier.close();
            }
        }
    }
}

 类似资料: