在finally块中引发异常


100

有没有一种优雅的方法来处理finally块中引发的异常?

例如:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

如何避免在try/ catchfinally块?

Answers:


72

我通常这样做:

try {
  // Use the resource.
} catch( Exception ex ) {
  // Problem with the resource.
} finally {
  // Put away the resource.
  closeQuietly( resource );
}

别处:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}

4
是的,我使用了非常相似的习语。但是我没有为此创建函数。
OscarRyz

9
如果您需要在同一个类中的几个地方使用该习语,则该函数非常方便。
达伦(Darron)在2009年

空值检查是多余的。如果资源为空,则应修复调用方法已损坏的问题。另外,如果资源为空,则可能应该记录该资源。否则,它将导致潜在的异常被静默忽略。
戴夫·贾维斯

14
空值检查并不总是多余的。将“ resource = new FileInputStream(“ file.txt”)”视为尝试的第一行。同样,这个问题也不是很多人不使用的面向方面的编程。但是,通过显示log语句可以最紧凑地处理不应该忽略Exception的概念。
达伦(Darron)2010年

1
Resource=> Closeable吗?
德米特里·金茨堡

25

我通常使用以下closeQuietly方法之一org.apache.commons.io.IOUtils

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}

3
您可以使用Closeable public static void closeQuietly(Closeable closeable){
Peter Lawrey

6
是的,Closeable很不错。许多事情(例如JDBC资源)没有实现它,真是可惜。
达伦(Darron)在2009年

22

如果您使用Java 7并resource实现AutoClosable,则可以执行此操作(以InputStream为例):

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}

8

可以说有点过高,但是如果让异常冒出气泡并且您无法在方法内记录任何内容(例如,因为它是一个库并且您希望让调用代码处理异常和记录),则可能有用:

Resource resource = null;
boolean isSuccess = false;
try {
    resource = Resource.create();
    resource.use();
    // Following line will only run if nothing above threw an exception.
    isSuccess = true;
} finally {
    if (resource != null) {
        if (isSuccess) {
            // let close throw the exception so it isn't swallowed.
            resource.close();
        } else {
            try {
                resource.close();
            } catch (ResourceException ignore) {
                // Just swallow this one because you don't want it 
                // to replace the one that came first (thrown above).
            }
        }
    }
}

更新:我对此进行了更多研究,并从比我更清楚地思考此问题的人那里找到了一篇很棒的博客文章:http : //illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html 他进一步走了一步,将两个例外合并为一个,在某些情况下我认为这很有用。


1
+1为博客链接。此外,我至少会记录该ignore异常
Denis Kniazhev 2011年

6

从Java 7开始,您不再需要在finally块中显式关闭资源,而可以使用try -with-resources语法。try-with-resources语句是一个try语句,用于声明一个或多个资源。资源是程序完成后必须关闭的对象。try-with-resources语句可确保在语句末尾关闭每个资源。任何实现java.lang.AutoCloseable的对象(包括所有实现java.io.Closeable的对象)都可以用作资源。

假设以下代码:

try( Connection con = null;
     Statement stmt = con.createStatement();
     Result rs= stmt.executeQuery(QUERY);)
{  
     count = rs.getInt(1);
}

如果发生任何异常,将以创建它们的相反顺序在这三个资源上分别调用close方法。这意味着将首先对ResultSetm,然后对Statement,最后对Connection对象,调用close方法。

同样重要的是要知道抑制了自动调用close方法时发生的任何异常。可以通过Throwable类中定义的getsuppressed()方法来检索这些受抑制的异常。

资料来源:https : //docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


这个答案似乎没有提及这种方法与OP发布的示例代码的工作方式之间的行为差​​异,这似乎并不完整。
内森·休斯

2
如果try块中的部分正常完成,但使用try-with-resources会在关闭时引发异常,但与OP代码不同,close方法不会。建议您将其推荐为替代产品而未确认行为有所改变,这可能会引起误解。
内森·休斯

它不会引发异常,不会自动调用close方法。
Soroosh 2015年

2
尝试我描述的情况。try块正常完成,关闭会抛出一些异常。并重新阅读您发布链接的页面,仅当try块抛出异常时才应用抑制。
内森·休斯 Nathan Hughes)2015年

3

忽略发生在“最终”块中的异常通常是一个坏主意,除非人们知道这些异常将是什么以及它们将代表什么条件。在正常try/finally使用模式中,该try块将事物置于外部代码不会期望的状态,并且该finally块将这些事物的状态恢复为外部代码所期望的状态。捕获异常的外部代码通常会期望,尽管有异常,但所有内容都已还原到normal州。例如,假设一些代码启动了一个事务,然后尝试添加两个记录;“最终”块执行“如果未提交则回滚”操作。调用者可能已为在执行第二个“添加”操作期间发生异常做好了准备,并且可能期望如果捕获到此类异常,则数据库将处于尝试执行任一操作之前的状态。但是,如果在回滚期间发生第二个异常,则如果调用者对数据库状态做出任何假设,则可能会发生不好的事情。回滚失败代表了一个重大危机-不应由仅预期“无法添加记录”异常的代码抓住。

我个人倾向于采用final方法来捕获发生的异常,并将其包装在“ CleanupFailedException”中,这要认识到此类故障是一个主要问题,并且不应轻易捕获此类异常。


2

一种解决方案,如果两个异常是两个不同的类

try {
    ...
    }
catch(package1.Exception err)
   {
    ...
   }
catch(package2.Exception err)
   {
   ...
   }
finally
  {
  }

但是有时您无法避免第二次尝试捕获。例如用于关闭流

InputStream in=null;
try
 {
 in= new FileInputStream("File.txt");
 (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
 }
catch(SQLException err)
 {
 //handle exception
 }
finally
 {
 //at the end, we close the file
 if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
 }

在您的情况下,如果您使用“ using”语句,则应清理资源。
查克·康威

不好,我假设它是C#。
查克·康威

1

为什么要避免额外的障碍?由于finally块包含“正常”操作,这些操作可能会引发异常,并且您希望finally块完全运行,因此您必须捕获异常。

如果您不希望finally块引发异常,并且不知道如何处理该异常(您将仅转储堆栈跟踪),则使异常冒泡到调用堆栈中(从finally中删除try-catch)块)。

如果要减少类型输入,可以实现“全局”外部try-catch块,该块将捕获在finally块中引发的所有异常:

try {
    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}

2
-1也是如此。如果您试图在一个finally块中关闭多个资源怎么办?如果关闭第一个资源失败,则抛出异常后,其他资源将保持打开状态。
Outlaw程序员,

这就是为什么我告诉Paul,如果您想确保finally块完成,则必须捕获异常。请阅读整个答案!
爱德华·维奇

1

经过大量考虑,我发现以下代码最佳:

MyResource resource = null;
try {
    resource = new MyResource();
    resource.doSomethingFancy();
    resource.close(); 
    resource = null;  
} finally {
    closeQuietly(resource)
}

void closeQuietly(MyResource a) {
    if (a!=null)
        try {
             a.close();
        } catch (Exception e) {
             //ignore
        }
}

该代码保证以下内容:

  1. 代码完成后释放资源
  2. 关闭资源时抛出的异常不会在不处理的情况下被消耗。
  3. 该代码不会尝试两次关闭资源,因此不会创建不必要的异常。

您还可以避免调用resource.close();。try块中的resource = null,这就是finally块的作用。还要注意,您不会处理“花哨的东西”时抛出的任何异常,实际上,我认为我更喜欢在更高级别的应用程序级别处理基础结构异常。
保罗

resource.close()也可能会抛出异常,即缓冲区刷新失败时。永远不要使用此异常。但是,如果由于先前引发的异常而关闭流,则应忽略该异常并保留根本原因,从而安静地关闭资源。
Grogi 2013年

0

如果可以,则应进行测试以避免出现错误情况。

try{...}
catch(NullArgumentException nae){...}
finally
{
  //or if resource had some useful function that tells you its open use that
  if (resource != null) 
  {
      resource.Close();
      resource = null;//just to be explicit about it was closed
  }
}

同样,您可能应该只捕获可以从中恢复的异常,如果无法恢复,则将其传播到程序的顶层。如果您无法测试错误条件,则必须像已经完成的那样用try catch块将代码包围起来(尽管我建议仍然捕获特定的预期错误)。


通常,测试错误条件是一种好习惯,因为异常的代价很高。
Dirk Vollmar,2009年

“防御性编程”是一种过时的范例。通过测试所有错误条件而导致的code肿代码最终会导致超出其解决范围的问题。TDD和处理例外情况是现代方法恕我直言
Joe Soul-bringer 09年

@Joe-我并不反对在所有错误条件下进行测试,但是有时候这样做是有道理的,特别是考虑到避免异常与避免异常本身的简单检查成本(通常)不同。
肯·亨德森

1
-1在这里,resource.Close()可以引发异常。如果您需要关闭其他资源,则异常将导致函数返回,并且它们将保持打开状态。这就是OP中第二次try / catch的目的。
Outlaw程序员,

@Outlaw-如果Close抛出异常,并且您打开了资源,然后捕获并抑制了异常,那么您将丢失我的观点我该如何解决此问题?因此,为什么要让它传播(这种情况很少见,即使打开仍可以恢复)。
肯·亨德森

0

您可以将此重构为另一种方法...

public void RealDoSuff()
{
   try
   { DoStuff(); }
   catch
   { // resource.close failed or something really weird is going on 
     // like an OutOfMemoryException 
   }
}

private void DoStuff() 
{
  try 
  {}
  catch
  {
  }
  finally 
  {
    if (resource != null) 
    {
      resource.close(); 
    }
  }
}

0

我通常这样做:

MyResource r = null;
try { 
   // use resource
} finally {   
    if( r != null ) try { 
        r.close(); 
    } catch( ThatSpecificExceptionOnClose teoc ){}
}

原理:如果我已经用完资源,而我唯一的问题就是关闭它,那么我就无能为力了。如果我已经用完资源,则杀死整个线程也没有任何意义。

至少对于我来说,这是一种情况,可以安全地忽略该检查的异常。

到今天为止,使用这个习语我还没有任何问题。


我将其记录下来,以防万一您将来发现一些泄漏。这样一来,您就知道他们可能来自
何处

@Egwor。我同意你的看法。这只是一些快速的摘要。我也将其记录下来,并且有可能使用捕获来解决:)
OscarRyz 2009年

0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

任务完成。没有空测试。单次捕获,包括获取和释放异常。当然,您可以使用Execute Around惯用语,而对于每种资源类型只需编写一次即可。


5
如果use(resource)引发异常A,然后resource.release()引发异常B怎么办?例外A丢失了……
达伦,

0

改变Resource最好的答案Closeable

流实现,Closeable因此您可以对所有流重用该方法

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}

0

我遇到过类似的情况,我无法使用try来处理资源,但是我也想处理来自close的异常,而不仅仅是像closeQuietly机制那样记录并忽略它。就我而言,我实际上并没有处理输出流,因此关闭失败比简单流更令人感兴趣。

IOException ioException = null;
try {
  outputStream.write("Something");
  outputStream.flush();
} catch (IOException e) {
  throw new ExportException("Unable to write to response stream", e);
}
finally {
  try {
    outputStream.close();
  } catch (IOException e) {
    ioException = e;
  }
}
if (ioException != null) {
  throw new ExportException("Unable to close outputstream", ioException);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.