>  기사  >  Java  >  Java의 AutoCloseable 인터페이스

Java의 AutoCloseable 인터페이스

coldplay.xixi
coldplay.xixi앞으로
2020-11-02 17:45:582671검색

오늘의 Basic Java Tutorial 칼럼에서는 AutoCloseable 인터페이스를 소개합니다.

Java의 AutoCloseable 인터페이스

이 글에서는 try-with-resources 구문을 더욱 심층적으로 분석하고, 이것이 일종의 구문 설탕임을 확인하며, 실제 구현의 디컴파일 결과도 제공합니다. 기사를 읽고 나면 AutoCloseable을 사용하여 새로운 통찰력을 얻을 수 있습니다.

1. 소개

최근에는 JDK 7의 새로운 기능인 try-with-resources 구문을 사용했습니다. 코드가 비교적 간단하다고 느꼈기 때문에 좀 더 자세히 공부한 결과를 공유하겠습니다. 아래에서 당신과 함께.

2. 간단한 이해와 사용

try-with-resources 구문은 비교적 사용하기 쉽습니다. 일반적으로 샘플 코드를 검색하고 보면 됩니다. 이 구문에 대한 JDK의 지원은 리소스를 더 잘 관리하거나 더 정확하게 말하면 리소스를 해제하는 것입니다.

리소스 클래스가 이 인터페이스의 close 메소드를 구현하는 경우 try-with-resources 구문을 사용하여 생성된 리소스에서 예외가 발생한 후 JVM은 예외가 발생하지 않을 때 자동으로 close 메소드를 호출하여 리소스를 해제합니다. try 코드 블록은 정상적으로 종료됩니다. close 메소드도 자동으로 호출됩니다. 데이터베이스 연결 클래스인 Connection과 마찬가지로 io 클래스인 InputStream 또는 OutputStream은 모두 이 인터페이스를 직접 또는 간접적으로 구현합니다.

코드 예제를 통해 사용 방법을 알아봅시다. 먼저 AutoCloseable 인터페이스를 구현하는 클래스를 만듭니다.

public class Resource implements AutoCloseable{
    public void read() {
        System.out.println("do something");
    }

    @Override
    public void close() throws Exception {
        System.out.println("closed");
    }
}复制代码

1. 이 구문을 사용하지 않으면 적극적으로 close

public static void f1(){
    Resource resource = new Resource();
    try {
        resource.read();
    } finally{
        try {
            resource.close();
        } catch (Exception e) {
        }
    }
}复制代码

2해야 합니다. 코드가 더 우아하고 간결해졌습니다

public static void f2(){
    try(Resource resource = new Resource()) {
        resource.read();
    } catch (Exception e) {
    }
}复制代码

참고: read 메소드 자체는 예외를 발생시키지 않지만 코딩할 때 catch 블록이 필요하다는 아이디어가 있으므로 이 예외를 캡처하는 것이 예외임을 알 수 있습니다. 캡처된 닫기 메서드에 의해 발생됩니다.

3. Closeable 인터페이스로 변경하거나

Resource 클래스의 AutoCloseable 인터페이스를 Closeable로 변경합니다(아래 참조). 이때 close 메서드의 예외 서명을 IOException으로 변경해야 합니다. 그렇지 않으면 컴파일이 통과되지 않습니다. 다음 코드는 예외 서명을 충족하기 위해 IOException을 적극적으로 발생시킵니다. 그렇지 않으면 예외 서명을 삭제하고 예외 서명 없음으로 변경하라는 메시지가 표시됩니다. 따라서 Closeable 인터페이스를 구현한 후 예외 서명은 없거나 IOException 또는 해당 하위 클래스입니다.

public class Resource implements Closeable{
    public void read() {
        System.out.println("do something");
    }

    @Override
    public void close() throws IOException{
        System.out.println("closed");
        throw new IOException();
    }
}复制代码

그런 다음 Closeable과 AutoCloseable 사이의 관계를 살펴보고 Closeable은 상속 관계 외에도 Exception에서 IOException까지 close 메소드의 예외 서명을 약간 더 구체적으로 만든다는 것을 발견했습니다.

public interface AutoCloseable {
    //省略Java doc
    void close() throws Exception;
}复制代码
public interface Closeable extends AutoCloseable {
    ////省略Java doc
    public void close() throws IOException;
}复制代码

JDK에서 java.lang.AutoCloseable 또는 java.io.Closeable 인터페이스를 구현하든 try-with-resources 구문을 사용할 수 있습니다. 여기서 또 다른 차이점은 두 인터페이스의 패킷 경로 차이입니다.

3. Java 문서 읽기

1. AutoCloseable의 Java 문서

닫힐 때까지 리소스(예: 파일 또는 소켓 핸들)를 보유할 수 있는 개체입니다. 리소스 사양 헤더에 개체가 선언된 try-with-resources 블록을 종료합니다. 이 구성은 리소스 소진 예외 및 발생할 수 있는 오류를 방지하여 신속한 릴리스를 보장합니다.

이것은 가능하며 실제로 일반적입니다. 모든 하위 클래스나 인스턴스가 해제 가능한 리소스를 보유하지 않더라도 AutoCloseable을 구현하는 기본 클래스입니다. 완전한 일반성으로 작동해야 하는 코드의 경우 또는 AutoCloseable 인스턴스에 리소스 해제가 필요한 것으로 알려진 경우 try-with를 사용하는 것이 좋습니다. -resources 구성. 그러나 I/O 기반 및 비I/O 기반 양식을 모두 지원하는 java.util.stream.Stream과 같은 기능을 사용할 때 try-with-resources 블록은 일반적으로 비-를 사용할 때 불필요합니다. I/O 기반 양식.

위는 AutoCloseable의 전체 Java 문서입니다. 이는 대략 다음을 의미합니다.

파일 및 소켓과 같은 개체는 닫기 메서드를 호출한 후 보유하고 있는 리소스를 해제합니다. AutoCloseable 인터페이스를 구현하는 클래스의 객체를 인스턴스화하기 위해 try-with-resources 구문을 사용할 때 close() 메서드가 자동으로 호출되어 자원이 적시에 해제되고 가능한 자원 고갈 문제를 방지합니다.

모든 하위 클래스가 리소스를 해제해야 하는 것은 아니거나 모든 인스턴스가 해제해야 하는 리소스를 보유하고 있지 않더라도 실행 가능한 AutoCloseable 인터페이스를 구현하는 일부 기본 클래스를 자주 볼 수 있습니다. 작업이 일반적인 방식으로 끝나야 하거나 해당 인스턴스가 리소스를 해제해야 한다는 것을 알고 있는 경우 AutoCloseable 인터페이스를 구현하고 try-with-resources 구문을 사용하는 것이 좋습니다.

그런 다음 AutoCloseable.close 메서드에 대한 Java 문서를 살펴보겠습니다. 다음은 원본 텍스트입니다.

이 리소스를 닫고 모든 기본 리소스를 포기합니다. 이 메서드는 try-에 의해 관리되는 개체에서 자동으로 호출됩니다. 자원 포함 진술.

While this interface method is declared to throw Exception, implementers are strongly encouraged to declare concrete implementations of the close method to throw more specific exceptions, or to throw no exception at all if the close operation cannot fail.

Cases where the close operation may fail require careful attention by implementers. It is strongly advised to relinquish the underlying resources and to internally mark the resource as closed, prior to throwing the exception. The close method is unlikely to be invoked more than once and so this ensures that the resources are released in a timely manner. Furthermore it reduces problems that could arise when the resource wraps, or is wrapped, by another resource.

Implementers of this interface are also strongly advised to not have the close method throw InterruptedException.

This exception interacts with a thread's interrupted status, and runtime misbehavior is likely to occur if an InterruptedException is suppressed.

More generally, if it would cause problems for an exception to be suppressed, the AutoCloseable.close method should not throw it.

Note that unlike the java.io.Closeable#close close method of java.io.Closeable, this close method is not required to be idempotent. In other words, calling this close method more than once may have some visible side effect, unlike Closeable.close which is required to have no effect if called more than once.

However, implementers of this interface are strongly encouraged to make their close methods idempotent.

翻译如下:

此方法用于关闭资源,放弃任何底层基础资源。当使用try-with-resources语法管理对象时,close方法将在try代码块逻辑结束后自动被调用。

虽然AutoCloseable中的close方法被声明为抛出Exception这个异常,但是强烈建议实现类声明更加具体的异常,或者当close操作不允许失败时声明为不抛出任何异常。

实现者需要注意close方法操作失败的情况,强烈建议放弃底层资源,并在抛出异常前在内部将资源标注为不可用。close方法不太可能被反复调用,因此这样确保close被调用后资源被及时标志为释放。此外,这样还能减少资源被其他资源包装时可能出现的问题。

强烈建议实现者在实现close方法时不要抛出InterruptedException。InterruptedException与线程状态交互影响,如果处理不当,可能导致运行时错误。更一般的情况下,当一个异常的不当处理会导致题,AutoCloseable.close方法就不应该抛出这个异常。

与java.io.Closeable.close方法不同的是,AutoCloseable.close方法的调用不要求幂等。换句话说,多次调用AutoCloseable.close可能会产生一些可见的副作用,不像Closeable.close允许多次调用。然而,强烈建议实现者将close方法实现为幂等。

2、 Closeable 中的 Java doc

Closeable类上的Java doc无额外有用信息,我们看下Closeable.close方法上的Java doc:

Closes this stream and releases any system resources associated with it. If the stream is already closed then invoking this method has no effect.

As noted in AutoCloseable#close(), cases where the close may fail require careful attention. It is strongly advised to relinquish the underlying resources and to internally mark the Closeable as closed, prior to throwing the IOException.

翻译如下:

此方法用于关闭流并释放与之相关的任何系统资源。

如果流已经关闭,则close方法的调用将无任何效果。如同AutoCloseable#close()的Java doc描述的那样,需要关注关闭失败的情况。

在抛出IOException异常前,强烈建议放弃底层资源并在内部标注资源已关闭。

四、揭开魔术面纱

1、怎么实现的

由于不确定try-with-resources是一种语法糖,还是在JVM虚拟机层面新加指令进行的支持,我尝试使用javap解析class文件后,通过解析结果查表对照发现,两段代码不同之处对应的字节码指令并不是此语法的含义,判定其确实是一种语法糖,在变成class文件的时候已经由编译器(javac)处理了。既然如此,我们就直接将其反编译,得到的结果如下:

public static void f1() {
    Resource resource = new Resource();
    try {
        resource.read();
    } finally {
        try {
            resource.close();
        } catch (Exception var7) {
        }
    }
}复制代码
public static void f2() {
    try {
        Resource resource = new Resource();
        Throwable var1 = null;
        try {
            resource.read();
        } catch (Throwable var11) {
            var1 = var11;
            throw var11;
        } finally {
            if (resource != null) {
                if (var1 != null) {
                    try {
                        resource.close();
                    } catch (Throwable var10) {
                        var1.addSuppressed(var10);
                    }
                } else {
                    resource.close();
                }
            }
        }
    } catch (Exception var13) {
    }
}复制代码

可以看到代码片段1与原代码几乎一样,但是代码片段2与原代码确实大相径庭,接着我们来分析下:

**关注点1:**new Resource()操作被放到了try内部了,如果不用try-with-resources语法,我们一般放到try外面。

**关注点2:**resource.read()操作被捕获了Throwable,并通过赋值记录了下来。

**关注点3:**fianally中的if (resource != null)这一步骤是多余的,因为只有在new Resource()操作抛异常才会存在resource为空的情况,然而这个时候就不会执行到这里来了。

**关注点4:**此时一定要执行resource.close()了,当var1不为空(即resource.read()抛出了异常),则需捕获close可能抛出的异常,并调用var1.addSuppressed记录关联try中抛出的异常,我们后面再分析这步骤。

**关注点5:**由于在原始代码里我们捕获了close方法抛出的异常,因此这里当上一步的var1为空时可能抛出的异常需要在最外层捕获。

2、Throwable.addSuppressed

public final synchronized void addSuppressed(Throwable exception) {
    if (exception == this)
        throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);

    if (exception == null)
        throw new NullPointerException(NULL_CAUSE_MESSAGE);

    if (suppressedExceptions == null) // Suppressed exceptions not recorded
        return;

    if (suppressedExceptions == SUPPRESSED_SENTINEL)
        suppressedExceptions = new ArrayList<>(1);

    suppressedExceptions.add(exception);
}复制代码

这段代码我们只需要了解,每一个Throwable实例中有一个List类型的字段,本方法将入参中的异常添加到了这个List的末尾。

Appends the specified exception to the exceptions that were suppressed in order to deliver this exception. This method is thread-safe and typically called (automatically and implicitly) by the try-with-resources statement.

The suppression behavior is enabled unless disabled Throwable(String, Throwable, boolean, boolean) via a constructor. When suppression is disabled, this method does nothing other than to validate its argument.

Note that when one exception causes another exception, the first exception is usually caught and then the second exception is thrown in response. In other words, there is a causal connection between the two exceptions. In contrast, there are situations where two independent exceptions can be thrown in sibling code blocks, in particular in the try block of a try-with-resources statement and the compiler-generated finally block which closes the resource. In these situations, only one of the thrown exceptions can be propagated. In the try-with-resources statement, when there are two such exceptions, the exception originating from the try block is propagated and the exception from the finally block is added to the list of exceptions suppressed by the exception from the try block. As an exception unwinds the stack, it can accumulate multiple suppressed exceptions.

An exception may have suppressed exceptions while also being caused by another exception. Whether or not an exception has a cause is semantically known at the time of its creation, unlike whether or not an exception will suppress other exceptions which is typically only determined after an exception is thrown.

Note that programmer written code is also able to take advantage of calling this method in situations where there are multiple sibling exceptions and only one can be propagated.

翻译如下:

为了传递此异常(入参),将其附加到记录到this中的实例字段(List)中。这个方法是线程安全的,通常由try-with-resources语句(自动和隐式地)调用。除非通过Throwable(String, Throwable, boolean, boolean)构造方法来显示禁用,否则将启用这个行为。禁用后,此方法除了验证参数外不做其他任何事情。

注意,当一个异常引发另一个异常时,通常会捕获第一个异常,然后在响应中抛出第二个异常(就是抛出最上层的异常)。换句话说,这两个异常之间存在因果关系。当然也存在例外情况,可以在兄弟代码块中抛出两个独立的异常,特别是在try-with-resources语句的try块和编译器生成的finally块(用于关闭资源)中。在这些情况下,只能传播一个抛出的异常。在try-with-resources语句中,如果存在两个这样的异常,则传播源自try块的异常,并将来自finally块的异常记录到try代码块抛出的异常中。当你展开一个异常堆栈时,你可以看到它累积的多个记录的未抛出的异常(因为只能抛出一个异常,其他异常只能通过这个方式来记录)。

一个异常可能记录有未抛出的异常,同时其自身也是由另一个异常引起的。一个异常是否由其他异常引起在创建时就已经在语义上知道了(即通过构造函数),这与这个异常是否通过Throwable.addSuppressed方式记录有异常不同,后者通常只在抛出异常后才能知道。

注意,程序员编写的代码,也能够在有多个兄弟异常(兄弟异常可以理解为相互并无关联)且只能传播一个异常的情况下,利用调用此方法的优势。

阅读完这个Java doc注释后,建议结合Throwable类源码加深理解。

五、JDK 9中的改进

通过搜索学习,还了解到在 JDK 9中对此语法进行了改进,JSR334对其进行了描述,感兴趣的可以点击 此处 或者 此处 来进一步了解优化点。

六、最佳实践

本次学习总结出如下最佳实践:

  • try-with-resources 구문을 사용하면 예외 발생 여부에 관계없이 try-block이 실행된 후 리소스의 닫기 메서드가 호출됩니다.
  • try-with-resources 구문을 사용하여 여러 리소스를 생성하세요. try-block이 실행된 후 호출되는 닫기 메서드의 순서는 리소스 생성 순서와 반대입니다.
  • try-with-resources 구문을 사용하면 try-block 블록에서 예외가 발생한 후
  • 먼저 모든 리소스의 close 메서드(try()에 선언됨) 를 실행한 다음 catch에서 코드를 실행하고 마지막으로 실행합니다.
  • try의 ()에서 관리되는 리소스를 생성하는 동안 발생하는 예외는 이 try에 해당하는 catch에서 포착되어야 합니다.
  • 자동으로 호출되는 close 메소드는 선언된 예외를 표시합니다. 이 예외는 이 시도에 해당하는 catch에서 포착되어야 합니다.
  • try-catch-finally에 try/catch/finally에 각각 예외가 발생하고 어떤 예외를 발생시킬지 결정할 수 없는 경우 try에서 해당 예외를 발생시키고 Throwable.addSuppressed 관계를 통해 이를 기록해야 합니다.

관련 무료 학습 권장사항: java 기본 튜토리얼

위 내용은 Java의 AutoCloseable 인터페이스의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제