由编译器完成的 Java try-with-resource 实现

Posted

技术标签:

【中文标题】由编译器完成的 Java try-with-resource 实现【英文标题】:Java try-with-resource implementation done by compiler 【发布时间】:2021-03-30 13:33:54 【问题描述】:

我想知道,当异常发生时,try with resource 语句如何在进入 catch 块之前关闭资源。 当异常发生时,执行立即跳转到 catch 块。所以实际上 try-with-resource 会关闭资源。 为了更好地理解它是如何工作的,我决定看看编译器是如何实现它的。我编写了以下代码并编译了它。

public class Test

    public static void main(final String[] args) 
       //I used same JDK for compilation and execution.
        System.out.println("Java version: " + System.getProperty("java.version") + "\n");
        try(CloseMe me = new CloseMe(); 
                CloseMeToo meToo = new CloseMeToo())
                
                System.out.println("trying");
                throw new Exception("try failed");

             catch(Exception e) 
                System.out.println("failed");
                System.out.println("\n");
                System.out.println(e.getMessage());
                System.out.println(e.getSuppressed()[0].getMessage());
                System.out.println(e.getSuppressed()[1].getMessage());
            
    


class CloseMe implements AutoCloseable 
    @Override
    public void close() throws Exception 
        System.out.println("me closing!");
        throw new Exception("don't close me :o");
    


class CloseMeToo implements AutoCloseable 
    @Override
    public void close() throws Exception 
        System.out.println("meToo closing!");
        throw new Exception("don't close me too :O");
    

输出

Java version: 15.0.1

trying
meToo closing!
me closing!
failed


try failed
don't close me too :O
don't close me :o

然后我去了www.javadecompilers.com 并尝试了那里的反编译器。两个反编译器给出了不错的结果:CFR 0.150 和 Fernflower。 CFR 是最易读和最完整的,所以在这里发布。

public class Test

    public static void main(final String[] args) throws Throwable
        System.out.println("Java version: " + System.getProperty("java.version") + "\n");
        
        try 
            Throwable throwable = null;
            Object var2_4 = null;     //<-- where this variable is used?
            try 
                CloseMe me = new CloseMe();
                try 
                    CloseMeToo meToo = new CloseMeToo();
                    try 
                        System.out.println("trying");
                        throw new Exception("try failed");
                    
                    catch (Throwable throwable2) 
                        throwable = throwable2; //<-- I put this line to make it work
                        if (meToo != null) 
                            meToo.close();
                        
                        throw throwable2;
                    
                
                catch (Throwable throwable3) 
                    if (throwable == null) 
                        throwable = throwable3;
                     else if (throwable != throwable3) 
                        throwable.addSuppressed(throwable3);
                    
                    if (me != null) 
                        me.close();
                    
                    throw throwable;
                
            
            catch (Throwable throwable4) 
                if (throwable == null) 
                    throwable = throwable4;
                 else if (throwable != throwable4) 
                    throwable.addSuppressed(throwable4);
                
                throw throwable;
            
        
        catch (Exception e) 
            System.out.println("failed");
            System.out.println("\n");
            System.out.println(e.getMessage());
            System.out.println(e.getSuppressed()[0].getMessage());
            System.out.println(e.getSuppressed()[1].getMessage());
        
    

我了解反编译器有局限性。理想的反编译器会给我同样的资源尝试,我不会看到这些细节。所以没关系。 我的问题是:

    在上述反编译代码中,变量var2_4 未使用。此外,我必须添加一行以使其像 try-with-resource 一样工作。我认为代码不完整。如果有的话,你能补充/解释缺失的部分吗? 如果有懂字节码的人将类文件翻译成准确的 java 代码,那就太好了。或者告诉我在哪里可以找到完成这项工作的工具。

谢谢!

【问题讨论】:

编译器已经提供了编译器将 try-with-resources 翻译成的基本版本。这不应该与规范一起就足够了吗? ***.com/a/17356707/1420279 【参考方案1】:

try-with-resources 的行为在Java Language Specification 部分14.20.3. try-with-resources 中完全记录

具体说明问题代码如下简写:

try (CloseMe me = new CloseMe(); CloseMeToo meToo = new CloseMeToo()) 
    System.out.println("trying");
 catch (Exception e) 
    System.out.println("failed");

首先转换为:

try 
    try (CloseMe me = new CloseMe(); CloseMeToo meToo = new CloseMeToo()) 
        System.out.println("trying");
    
 catch (Exception e) 
    System.out.println("failed");

然后到:

try 
    final CloseMe me = new CloseMe();
    Throwable #primaryExc1 = null;

    try (CloseMeToo meToo = new CloseMeToo()) 
        System.out.println("trying");
     catch (Throwable #t) 
        #primaryExc1 = #t;
        throw #t;
     finally 
        if (me != null) 
            if (#primaryExc1 != null) 
                try 
                    me.close();
                 catch (Throwable #suppressedExc) 
                    #primaryExc1.addSuppressed(#suppressedExc);
                
             else 
                me.close();
            
        
    
 catch (Exception e) 
    System.out.println("failed");

然后到:

try 
    final CloseMe me = new CloseMe();
    Throwable #primaryExc1 = null;

    try 
        final CloseMeToo meToo = new CloseMeToo()
        Throwable #primaryExc2 = null;

        try 
            System.out.println("trying");
        catch (Throwable #t) 
            #primaryExc2 = #t;
            throw #t;
         finally 
            if (meToo != null) 
                if (#primaryExc2 != null) 
                    try 
                        meToo.close();
                     catch (Throwable #suppressedExc) 
                        #primaryExc2.addSuppressed(#suppressedExc);
                    
                 else 
                    meToo.close();
                
            
        
     catch (Throwable #t) 
        #primaryExc1 = #t;
        throw #t;
     finally 
        if (me != null) 
            if (#primaryExc1 != null) 
                try 
                    me.close();
                 catch (Throwable #suppressedExc) 
                    #primaryExc1.addSuppressed(#suppressedExc);
                
             else 
                me.close();
            
        
    
 catch (Exception e) 
    System.out.println("failed");

【讨论】:

请注意,实际的编译器实现使用无法在语言级别表示的 goto。

以上是关于由编译器完成的 Java try-with-resource 实现的主要内容,如果未能解决你的问题,请参考以下文章

Java代码编译和执行的整个过程

详细理解Java虚拟机的运行过程

JVM系列五(javac 编译器)

JVM系列五(javac 编译器).

JAVA类加载机制详解

JVM执行引擎篇(解释器与编译器)