Закройте ресурс тихо, используя 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) {
      }
    }
  }
}

Ответ 1

Я нашел, что это ответ на список рассылки монеты-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;
    }
}

Ответ 2

Вы можете использовать шаблон декоратора здесь, чтобы закрыть ресурс тихо:

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 enablers. Однако эти решения будут работать в явном виде с JDK7 и другими зависимостями.

Ответ 3

Это одно из решений:

    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==false, e происходит от read() или конструктора. close() все равно будет вызываться и может бросать e2, но e2 все равно будет подавлен.

Код вполне читабельен, не пройдя такой анализ. Интуитивно он говорит, если ok==true, наша настоящая работа выполнена, и нам все равно, какие ошибки возникают после этого относительно ресурса.