Java 是否有嵌套try/catch块的首选项?

Java 是否有嵌套try/catch块的首选项?,java,try-catch,Java,Try Catch,在Java中使用读卡器和流时,我经常遇到的一个问题是close()方法会引发异常。由于将close方法放在finally块中是一个好主意,因此需要一点尴尬的情况。我通常使用这种结构: FileReader fr = new FileReader("SomeFile.txt"); try { try { fr.read(); } finally { fr.close(); } } catch(Exception e) { // Do

在Java中使用读卡器和流时,我经常遇到的一个问题是
close()
方法会引发异常。由于将close方法放在finally块中是一个好主意,因此需要一点尴尬的情况。我通常使用这种结构:

FileReader fr = new FileReader("SomeFile.txt");
try {
    try {
        fr.read();
    } finally {
        fr.close();
    }
} catch(Exception e) {
    // Do exception handling
}
FileReader fr = new FileReader("SomeFile.txt");
try {
    fr.read() 
} catch (Exception e) {
    // Do exception handling
} finally {
    try {
        fr.close();
    } catch (Exception e) {
        // Do exception handling
    }
}
但我也看到了这种结构:

FileReader fr = new FileReader("SomeFile.txt");
try {
    try {
        fr.read();
    } finally {
        fr.close();
    }
} catch(Exception e) {
    // Do exception handling
}
FileReader fr = new FileReader("SomeFile.txt");
try {
    fr.read() 
} catch (Exception e) {
    // Do exception handling
} finally {
    try {
        fr.close();
    } catch (Exception e) {
        // Do exception handling
    }
}
我更喜欢第一个建筑,因为只有一个挡块,看起来更优雅。是否有理由选择第二种或另一种结构


更新:如果我指出
read
close
都只抛出IOException,会有什么不同吗?因此,在我看来,如果读取失败,close也会因为同样的原因失败。

据我所知,不同的是,在不同的级别上存在不同的异常和原因,并且

捕获(例外e)

掩盖了这一点。多个级别的唯一目的是区分您的异常,以及您将如何处理它们:

try
{
  try{
   ...
  }
   catch(IOException e)
  {
  ..
  }
}
catch(Exception e)
{
  // we could read, but now something else is broken 
  ...
}

恐怕第一个示例存在一个大问题,即如果在读取时或读取后发生异常,
最后执行
块。到现在为止,一直都还不错。但是如果
fr.close()
导致抛出另一个异常,该怎么办?这将“压倒”第一个异常(有点像将
return
放在
finally
块中),并且您将丢失有关问题实际原因的所有信息

您的finally块应使用:

IOUtil.closeSilently(fr);
此实用程序方法仅用于:

public static void closeSilently(Closeable c) {
    try { c.close(); } catch (Exception e) {} 
} 

我更喜欢第二个。为什么?如果
read()
close()
都抛出异常,其中一个可能会丢失。在第一种构造中,来自
close()
的异常覆盖来自
read()
的异常,而在第二种构造中,来自
close()
的异常被单独处理


从Java 7开始,这就简单多了。阅读时不必考虑异常:

try (FileReader fr = new FileReader("SomeFile.txt")) {
    fr.read();
    // no need to close since the try-with-resources statement closes it automatically
}
对于异常处理:

try (FileReader fr = new FileReader("SomeFile.txt")) {
    fr.read();
    // no need to close since the try-with-resources statement closes it automatically
} catch (IOException e) {
    // Do exception handling
    log(e);
    // If this catch block is run, the FileReader has already been closed.
    // The exception could have come from either read() or close();
    // if both threw exceptions (or if multiple resources were used and had to be closed)
    // then only one exception is thrown and the others are suppressed
    // but can still be retrieved:
    Throwable[] suppressed = e.getSuppressed(); // can be an empty array
    for (Throwable t : suppressed) {
        log(suppressed[t]);
    }
}

只需要一个try-catch,所有异常都可以安全处理。如果愿意,您仍然可以添加
finally
块,但无需关闭资源。

我使用的标准约定是,您不能让异常逃逸finally块

这是因为如果一个异常已经在传播,那么从finally块抛出的异常将超过原始异常(并因此丢失)

在99%的情况下,这不是您想要的,因为原始异常可能是问题的根源(任何次要异常都可能是第一个异常的副作用,但会模糊您找到原始异常来源的能力,从而导致真正的问题)

因此,您的基本代码应该如下所示:

try
{
    // Code
}
// Exception handling
finally
{
    // Exception handling that is garanteed not to throw.
    try
    {
         // Exception handling that may throw.
    }
    // Optional Exception handling that should not throw
    finally()
    {}
}

我总是选择第一个例子

如果close抛出一个异常(实际上,对于FileReader来说,这是永远不会发生的),那么标准的处理方法不是抛出一个适合调用方的异常吗?几乎可以肯定,close异常胜过了您在使用资源时遇到的任何问题。如果异常处理的想法是调用System.err.println,那么第二种方法可能更合适

有一个问题是异常应该抛出多远。ThreadDeath应该总是被重新调用,但是finally中的任何异常都会阻止它。类似地,错误应该抛出比RuntimeException更远的异常,RuntimeException应该抛出比已检查异常更远的异常。如果您真的想这样做,您可以编写遵循这些规则的代码,然后使用“围绕执行”成语对其进行抽象。

如果read和close都抛出异常,那么read的异常将隐藏在选项1中。因此,第二个选项执行更多的错误处理

然而,在大多数情况下,第一种选择仍然是首选

  • 在许多情况下,您无法在生成异常的方法中处理异常,但仍然必须在该操作中封装流处理
  • 尝试向代码中添加一个编写器,看看第二种方法有多冗长

  • 如果需要传递所有生成的异常,我通常会执行以下操作。首先,定义一个基于模板方法的类来处理try/catch混乱

    import java.io.Closeable;
    import java.io.IOException;
    import java.util.LinkedList;
    import java.util.List;
    
    public abstract class AutoFileCloser {
        private static final Closeable NEW_FILE = new Closeable() {
            public void close() throws IOException {
                // do nothing
            }
        };
    
        // the core action code that the implementer wants to run
        protected abstract void doWork() throws Throwable;
    
        // track a list of closeable thingies to close when finished
        private List<Closeable> closeables_ = new LinkedList<Closeable>();
    
        // mark a new file
        protected void newFile() {
            closeables_.add(0, NEW_FILE);
        }
    
        // give the implementer a way to track things to close
        // assumes this is called in order for nested closeables,
        // inner-most to outer-most
        protected void watch(Closeable closeable) {
            closeables_.add(0, closeable);
        }
    
        public AutoFileCloser() {
            // a variable to track a "meaningful" exception, in case
            // a close() throws an exception
            Throwable pending = null;
    
            try {
                doWork(); // do the real work
    
            } catch (Throwable throwable) {
                pending = throwable;
    
            } finally {
                // close the watched streams
                boolean skip = false;
                for (Closeable closeable : closeables_) {
                    if (closeable == NEW_FILE) {
                        skip = false;
                    } else  if (!skip && closeable != null) {
                        try {
                            closeable.close();
                            // don't try to re-close nested closeables
                            skip = true;
                        } catch (Throwable throwable) {
                            if (pending == null) {
                                pending = throwable;
                            }
                        }
                    }
                }
    
                // if we had a pending exception, rethrow it
                // this is necessary b/c the close can throw an
                // exception, which would remove the pending
                // status of any exception thrown in the try block
                if (pending != null) {
                    if (pending instanceof RuntimeException) {
                        throw (RuntimeException) pending;
                    } else {
                        throw new RuntimeException(pending);
                    }
                }
            }
        }
    }
    
    使用这种方法,您不必担心try/catch/finally会再次关闭文件

    如果这对于您的使用来说太重了,至少考虑遵循try/catch和它使用的“挂起”变量方法。

    2nd方法

    否则,我看不到您从FileReader构造函数捕获异常

    公共文件读取器(字符串文件名) 抛出FileNotFoundException

    因此,我通常在try块中也有构造函数。在尝试关闭之前,finally块检查读取器是否为null


    数据源、连接、语句、结果集的模式相同。

    < P>有时嵌套Testcatch不是首选项,考虑如下:

    try{
     string s = File.Open("myfile").ReadToEnd(); // my file has a bunch of numbers
     // I want to get a total of the numbers 
     int total = 0;
     foreach(string line in s.split("\r\n")){
       try{ 
         total += int.Parse(line); 
       } catch{}
     }
    catch{}
    

    这可能是一个不好的例子,但有时您需要嵌套的try cacch。

    我喜欢@Chris Marshall的方法,但我从不喜欢看到异常被默默吞没。我认为最好是记录异常,特别是如果你继续不管

    我总是使用一个实用类来处理这类常见的异常,但我会让这一点与他的答案有所不同

    我总是使用一个记录器(log4j为我)来记录错误等

    IOUtil.close(fr);
    
    对实用方法稍作修改:

    public static void close(Closeable c) {
        try {
          c.close();
        } catch (Exception e) {
          logger.error("An error occurred while closing. Continuing regardless", e); 
        } 
    }
    

    在某些情况下,嵌套的Try-Catch是不可避免的。例如,当错误恢复代码本身可以引发错误和异常时。但是为了提高代码的可读性,您总是可以将嵌套块提取到它自己的方法中。查看博客文章,了解更多关于嵌套Try-Catch-Finally块的示例。

    请告诉我。注意: