使用try-with-resources静静地关闭资源

时间:2011-07-31 13:16:43

标签: java exception java-7 try-with-resources

是否可以忽略使用try-with-resources语句关闭资源时抛出的异常?

示例:

class MyResource implements AutoCloseable{
  @Override
  public void close() throws Exception {
    throw new Exception("Could not close");
  }  
  public void read() throws Exception{      
  }
}

//this method prints an exception "Could not close"
//I want to ignore it
public static void test(){
  try(MyResource r = new MyResource()){
    r.read();
  } catch (Exception e) {
    System.out.println("Exception: " + e.getMessage());
  }
}

或者我应该继续关闭finally吗?

public static void test2(){
  MyResource r = null;
  try {
     r.read();
  }
  finally{
    if(r!=null){
      try {
        r.close();
      } catch (Exception ignore) {
      }
    }
  }
}

4 个答案:

答案 0 :(得分:24)

我在coin-dev邮件列表上找到了这个答案: http://mail.openjdk.java.net/pipermail/coin-dev/2009-April/001503.html

  

<强> 5。可以安全地忽略close方法的某些失败(例如,   关闭一个打开以供阅读的文件。构造是否提供   此吗

     

没有。虽然这个功能看起来很吸引人,但目前尚不清楚   值得增加的复杂性。作为一个实际问题,这些“无害   例外“很少发生,所以程序将不再强大   如果忽略这些例外。如果你觉得你必须忽略它们,   有一种解决方法,但它并不漂亮:

static void copy(String src, String dest) throws IOException {
    boolean done = false;
    try (InputStream in = new FileInputStream(src)) {
        try(OutputStream out = new FileOutputStream(dest)) {
            byte[] buf = new byte[8192];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        }
        done = true;
    } catch(IOException e) {
        if (!done)
            throw e;
    }
}

答案 1 :(得分:19)

您可以在此处使用装饰器模式静静地关闭资源:

public class QuietResource<T extends AutoCloseable> implements AutoCloseable{
    T resource;
    public QuietResource(T resource){
        this.resource = resource;
    }
    public T get(){
        return resource;
    }
    @Override
    public void close() {
        try {
            resource.close();
        }catch(Exception e){
            // suppress exception
        }
    }  
}

我个人并不喜欢结果语法,但也许这对您有用:

public static void test(){
    try(QuietResource<MyResource> qr = new QuietResource<>(new MyResource())){
        MyResource r = qr.get();
        r.read();
    } catch (Exception e) {
        System.out.println("Exception: " + e.getMessage());
    }
}

如果您愿意限制自己处理接口并利用动态代理类,那么您可以做得更好:

public class QuietResource<T> implements InvocationHandler {

    private T resource;

    @SuppressWarnings("unchecked")
    public static <V extends AutoCloseable> V asQuiet(V resource){
        return (V) Proxy.newProxyInstance(
                resource.getClass().getClassLoader(),
                resource.getClass().getInterfaces(),
                new QuietResource<V>(resource));
    }

    public QuietResource(T resource){
        this.resource = resource;
    }

    @Override
    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
        if(m.getName().equals("close")){
            try {
                return m.invoke(resource, args);
            }catch(Exception e){
                System.out.println("Suppressed exception with message: " + e.getCause().getMessage());
                // suppress exception
                return null;
            }
        }
        return m.invoke(resource, args);
    }
}

然后假设你有:

public interface MyReader extends AutoCloseable{
    int read();
}

使用实际资源类:

public class MyResource implements MyReader {

    public void close() throws Exception{
        throw new Exception("ha!");
    }

    public int read(){
        return 0;
    }
}

调用语法如下:

public static void test(){
    try(MyReader r = QuietResource.asQuiet(new MyResource())){
        r.read();
    } catch (Exception e) {
        System.out.println("Exception: " + e.getMessage());
    }
}

如果你想开始包括库,比如AOP启动器,你可以做得更好。但是,这些解决方案将与JDK7一起开箱即用,而且没有其他依赖关系。

答案 2 :(得分:4)

这是一个解决方案:

    boolean ok=false;
    try(MyResource r = new MyResource())
    {
        r.read();
        ok=true;
    }
    catch (Exception e)
    {
        if(ok)
            ; // ignore
        else
            // e.printStackTrace();
            throw e;
    }

如果ok==true我们得到例外,则肯定来自close()

如果ok==falsee来自read()或构造函数。 close()仍会被调用,可能会抛出e2,但无论如何都会抑制e2。

代码非常易读,无需经过此类分析。直观地说,如果ok==true,我们的实际工作已经完成,我们并不关心资源之后出现的错误。

答案 3 :(得分:0)

我实际上并不推荐这样做,但我能想到的唯一方法是检查异常的堆栈跟踪。它来自附近的 close 方法吗?

基于 https://stackoverflow.com/a/32753924/32453,任何捕获的异常都将是来自主块的“异常”、来自关闭调用的异常,或者来自带有“抑制”关闭调用的 try 块的异常。

所以你只需要弄清楚它是否是 close 调用本身的异常,显然是 catch 的行:

try (Resource myResource = new Resource()) {

} catch (IOException mightBeFromClose) {
  int currentLine = new Throwable().getStackTrace()[0].getLineNumber();
  int lineOfCatch = currentLine - 1;
  String currentFilename = new Throwable().getStackTrace()[0].getFileName();
  boolean exceptionWasFromClose = Stream.of(mightBeFromClose.getStackTrace()).anyMatch(l -> l.getFileName().equals(currentFilename) && l.getLineNumber() == lineOfCatch);
  if (exceptionWasFromClose) {
    // ...
  }
}

还有几点需要考虑:

一般来说,不清楚您是否想要处理来自不同于 try 块内部的 IOException 调用的 close。如果关闭调用意味着它没有将所有数据刷新到文件中怎么办?您可能想一视同仁地处理/对待它们。

另一种选择:在块末尾附近手动关闭资源(使用自己的 try-catch)。通常允许双重关闭,因此您可以在那里捕获关闭异常。

另一种可能性:改用普通的 try-catch-finally 模式,这里有一些方法可以让它稍微不那么难看:如果您没有多个资源,Java try-finally inside try-catch pattern 可能是一个选项。