Catch errors during object construction from try-with-resources separately from body

2.1k views Asked by At

Summary

I have a closeable type, CloseableClass that can throw an IOError in its constructor, methods and maybe even inside close. I want to use try-with-resources and still deal with errors during construction differently to errors during use (use includes cleanup). Even better, I would like to write maintainable code.


Let's say you wish to construct a closeable class instance and use it with a try-with-resources statement. It can throw IOException in both its constructor and the method used in the body of the try-with-resources:

import java.io.Closeable;
import java.io.IOException;
import java.util.Random;

public class CloseableClass implements Closeable {
    public CloseableClass() throws IOException {
        if (new Random().nextBoolean()) {
            throw new IOException();
        }
    }

    public void internetStuff() throws IOException {
        if (new Random().nextBoolean()) {
            throw new IOException();
        }
    }

    public void close() throws IOException {
        if (new Random().nextBoolean()) {
            throw new IOException();
        }
    }

    public static void main(String[] args) {
        try (CloseableClass closeable = new CloseableClass()) {
            closeable.internetStuff();
        }
        catch (IOException e) {
            System.out.println("Bad error!");
        }
    }
}

Let's say you want to deal with the errors thrown in the constructor and the body separately. Is there a supported way to do that? In Python I would do:

try:
    closeable = CloseableClass()
except IOException:
    print("Constructor error")
    return

try:
    with closeable:
        closeable.internet_stuff()
except IOException:
    print("Body error")

but in Java you cannot without assigning a second name to the object:

CloseableClass closeable_;

try {
    closeable_ = new CloseableClass();
}
catch (IOException e) {            
    System.out.println("Constructor error!");
    return;
}

try (CloseableClass closeable = closeable_) {
    closeable.internetStuff();
}
catch (IOException e) {
    System.out.println("Body error!");
}

I have been told that this is "unmaintainable code" primarily due to the use of closeable_, and I'm not far from agreeing. I wish to avoid using try-finally because then you have the even worse problem of emulating it:

CloseableClass closeable;

try {
    closeable = new CloseableClass();
}
catch (IOException e) {            
    System.out.println("Constructor error!");
    return;
}

try {
    closeable.internetStuff();
}
catch (IOException e) {
    try {
        closeable.close();
    }
    catch (IOException ignore) {
        // Already dealing with this
    }

    System.out.println("Body error!");
}
finally {
    try {
        closeable.close();
    }
    catch (IOException e) {
        System.out.println("Body error!");
    }
}

Note that this requires a second call to close to be a no-op, which the test class doesn't abide by (note that AutoCloseable doesn't require this, although Closeable does). This is a tad nicer when close can't throw, but not much.

Basically the problem is that

  • close can throw
  • Close before dealing with IOException to prevent printing "Body error!" twice
  • It's not obvious how to make it work with multiple initializers from the try-with-resources
  • You end up duplicating code anyway.

Am I just forced to live with the "unmaintainable code" or am I overlooking a good method to deal with this?

3

There are 3 answers

0
Veedrac On BEST ANSWER

Since Java 9, try-with-resources has accepted ‘effectively final’ variables, so you do not need to reassign the variable.

CloseableClass closeable;

try {
    closeable = new CloseableClass();
}
catch (IOException e) {            
    System.out.println("Constructor error!");
    return;
}

try (closeable) {
    closeable.internetStuff();
}
catch (IOException e) {
    System.out.println("Body error!");
}
6
brb tea On

'Note that this requires a second call to close to be a no-op' - No you don't need close() in catch block, as finally block will always going to be executed. You will only use close() inside catch block if you terminate JVM with call like System.exit() in catch block. Generally you will throw Exception to caller from catch clock, but you will perform cleanups in finally block most of time.

Try-with-resource is better, but you can use type and description of Exception thrown to decipher what and where went wrong.

EDIT

Per my knowledge I suggest:

1) Try with resource:

try(Resource resource = new Resource()){
    // use resource.
}catch(Exception e){
    // handle exception.
    // OR better to throw exception to caller.
    throw e;
}

2) Conventional style:

Resource resource = null;
try{
    resource = new Resource();
    // use resource
}catch(Exception e){
    // handle exception.
    // OR better to throw exception to caller.
    throw e;
} finally {
   if(resource != null){
       try{
           resource.close();
       } catch(Exception e){
           // most of time you wont or cant do anything here.
       }
   }
}
0
augurar On

One solution is to define a method which wraps initialization errors in a custom exception type, and then use that to determine when errors occurred.

private CloseableClass createCloseable() throws CloseableCreateException{
    try {
        return new CloseableClass();
    } except (IOException e) {
        throw new CloseableCreateException(e);
    }
}
try (CloseableClass closeable = initCloseable()) {
    closeable.internetStuff();
} catch (CloseableCreateException e) {
    System.out.println("Constructor error!");
} catch (IOException e) {
    System.out.println("Body error!");
}

Another simple but somewhat inelegant solution is to use a boolean flag:

boolean init = true;
try (CloseableClass closeable = new CloseableClass()) {
    init = false;
    closeable.internetStuff();
} catch (IOException e) {
    if (init) {
        System.out.println("Constructor error!");
    } else {
        System.out.println("Body error!");
    }
}