When you want to use some AutoClosable
object you should use try-with-resources. Ok. But what if I want to write a method, that returns an AutoClosable
? After you've created or received an AutoCloseable object, you should close it in case of exception, like this:
public static AutoCloseable methodReturningAutocloseable() {
AutoCloseable autoCloseable = ... // create some AutoClosable
try {
... // some work
}
catch (Throwable exception) {
autoCloseable.close();
throw exception;
}
return autoCloseable;
}
If you don't write try/catch
block you would leak the resource, that autoCloseable object holds, in case of exception in // some work
line.
But this try/catch
is not enough, because autoCloseable.close()
can throw exception too (by design). So, the above code transforms to
public static AutoCloseable methodReturningAutocloseable() {
AutoCloseable autoCloseable = ... // create some autoclosable
try {
... // some work
}
catch (Throwable exception) {
try {
autoCloseable.close();
}
catch (Throwable exceptionInClose) {
exception.addSuppressed(exceptionInClose);
throw exception;
}
throw exception;
}
return autoCloseable;
}
That's a lot of boilerplate. Is there a better way to do it in java?
There are a number of approached.
AutoCloseable
and put that in your try-with-resource.Edit: I thought I'd revisit the answer adding some example code for fun.
Execute Around idiom
The simple and best solution. Unfortunately the Java library does not use it much (AccessController.doPrivileged
is a big exception) and conventions are not well established. As ever Java's checked exceptions without supporting features make things tricky. We can't use java.util.function
and have to invent our own functional interfaces.
// Like Consumer, but with an exception.
interface Use<R, EXC extends Exception> {
void use(R resource) throws EXC;
}
public static void withThing(String name, Use<InputStream,IOException> use) throws IOException {
try (InputStream in = new FileInputStream(name)) {
use.use(in);
}
}
Nice and simple. No need to worry about client code messing up the resource handling as it doesn't do it. Nice.
A modified try-with-resource as a library feature implemented as a proxy AutoCloseable
in a try-with-resource
It's going to get ugly. We need to pass acquisition, release and the initialisation as lambdas. Creating the resource directly within this method opens up a small window where an unexpected exception would lead to a leak.
public static InputStream newThing(String name) throws IOException {
return returnResource(
() -> new FileInputStream(name),
InputStream::close,
in -> {
int ignore = in.read(); // some work
}
);
}
The general implementation of returnResource
is going to look like this hack below. A hack because try-with-resource doesn't support this sort of thing and Java library doesn't support checked exceptions well. Note limited to one exception (you can use an unchecked exception for no checked exceptions).
interface Acquire<R, EXC extends Exception> {
R acquire() throws EXC;
}
// Effectively the same as Use, but different.
interface Release<R, EXC extends Exception> {
void release(R resource) throws EXC;
}
public static <R, EXC extends Exception> R returnResource(
Acquire<R, EXC> acquire, Release<R, EXC> release, Use<R, EXC> initialize
) throws EXC {
try (var adapter = new AutoCloseable() { // anonymous classes still define type
private R resource = acquire.acquire();
R get() {
return resource;
}
void success() {
resource = null;;
}
public void close() throws EXC {
if (resource != null) {
release.release(resource);
}
}
}) {
R resource = adapter.get();
initialize.use(resource);
adapter.success();
return resource;
}
}
This is perhaps cleaner if we separate out the argument we are constructing the resource with from the construction of the resource.
public static InputStream newThing(String name) throws IOException {
return returnResource(
name,
FileInputStream::new,
InputStream::close,
in -> {
int ignore = in.read(); // some work
}
);
}
// Like Function, but with a more descriptive name for a functional interface.
interface AcquireFrom<T, R, EXC extends Exception> {
R acquire(T t) throws EXC;
}
public static <T, R, EXC extends Exception> R returnResource(
T t, AcquireFrom<T, R, EXC> acquire, Release<R, EXC> release, Use<R, EXC> initialize
) throws EXC {
return returnResource(() -> acquire.acquire(t), release, initialize);
}
So in summary, the following things are a pain:
java.util.function
doesn't support checked exceptions.AutoCloseable::close
declaring that it throws Exception
instead of the being a type parameter of the type.