Fix errors when constructing an object from try-with-resources separately from the body
Summary
I have a private type CloseableClass
that can throw an IOError in its constructor, methods, and possibly even internally close
. I want to use try-with-resources and still handle build errors differently with errors during use (usage includes cleanup). Better yet, I would like to write maintainable code.
Suppose you want to construct an instance of a closable class and use it using a try-with-resources statement. It can be called IOException
both in its constructor and in the method used in the body of 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 errors generated in constructor and body separately. Is there a way to do this? 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 can't help but assign a second name to an 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 was told that this is "unreachable code" mainly due to usage closeable_
and I disagree. I want to avoid using try-finally, because then you have an even worse emulation problem:
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 close
, which should be non-working, which the test class doesn't honor (note that it AutoCloseable
doesn't require it, although Closeable
it does). It's a little nicer when close
it can't throw away, but not much.
Basically the problem is that
-
close
can throw - Close before starting
IOException
to prevent printing"Body error!"
twice - Not obvious how to make it work with multiple initializers from try-with-resources
- You are duplicating code anyway.
Am I just forced to live with "unreachable code" or am I missing a good way to deal with this?
source to share
'Note that this requires a second call to close the "no-op" - No, you don't need a block close()
in catch
as the block finally
will always be executed. You will only use close()
internally catch
if you terminate the JVM with a type call System.exit()
in catch
. Typically, you flip Exception
for the caller from the watch catch
, but most of the time you will be doing the cleanup in finally
.
Attempt-resource is better, but you can use type and description Exception
to decipher what went wrong where.
EDIT
In my knowledge, I suggest:
1) Try with a resource:
try(Resource resource = new Resource()){
// use resource.
}catch(Exception e){
// handle exception.
// OR better to throw exception to caller.
throw e;
}
2) Regular 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.
}
}
}
source to share
One solution is to define a method that packages the initialization errors into a custom exception type and then uses that to determine when the 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 tricky 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!");
}
}
source to share