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

39

使用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个回答

29

我在coin-dev的邮件列表中找到了这个答案:

http://mail.openjdk.java.net/pipermail/coin-dev/2009-April/001503.html

5. 关闭方法的一些失败可以安全地忽略(例如,关闭一个已经打开进行读取操作的文件)。该结构是否提供此功能?

没有。虽然这个功能看起来很有吸引力,但不清楚它是否值得增加复杂性。实际上,这些“无伤大雅的异常”很少发生,如果忽略这些异常,程序也不会更加健壮。如果你必须忽略它们,那么有一个解决办法,但并不是很完美:

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;
    }
}

现在还有什么其他的想法吗?它看起来不是很美观。 - Zhongqiang Pu

25
您可以在这里使用装饰器模式来静默关闭资源。
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上使用,无需其他依赖。


你应该从 close() 中删除 throws Exception,以记录捕获的异常并简化使用。 - Arne Burmeister
1
@Mark Elliot,你可以使用未声明任何throws的类方法来实现带有throws声明的接口方法。 - BegemoT

5
这是一个解决方案:
    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()或构造函数。即使会抛出e2异常,close()仍将被调用,但无论如何,e2都将被禁止。
如果不深入分析代码,它也很容易阅读。直观地说,如果ok==true,我们完成了真正的工作,并且我们不在意之后关于资源方面的任何错误。

-1

我并不真正推荐这样做,但我能想象的唯一方法是检查异常的堆栈跟踪。它是否来自附近的关闭方法?

根据https://dev59.com/yY_ea4cB1Zd3GeqPOG2Q#32753924,任何捕获的异常都将是主块中的“异常”,来自关闭调用的异常,或带有“抑制”关闭调用的try块中的异常。

因此,您只需要弄清楚它是否是关闭调用本身引起的异常,这显然是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) {
    // ...
  }
}

还有一些需要考虑的事情:

通常不清楚您是否希望处理close调用中的IOException与try块内部的异常不同。如果关闭调用意味着它没有将所有数据刷新到文件中,那该怎么办?您可能希望将它们全部处理/对待相同。

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

另一个可能性:使用正常的try-catch-finally模式,以下是使其稍微不那么丑陋的一些方法:如果您没有多个资源,则Java try-finally inside try-catch pattern可能是一个选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接