There are a number of approached.
- Use the Execute Around idiom. Reformulate the interface to ease client implementation and remove the problem.
- Ignore the problem. Sounds silly, but that's generally what happens when wrapping with I/O stream decorators.
- Wrap inside a proxy
AutoCloseable
and put that in your try-with-resource.
- Write out the equivalent of try-with-resource use try-catch and try-finally. (I wouldn't - nasty.)
- Write a modified try-with-resource as a library feature. This code would become the client of the Execute Around.
- Write the exception handling old school style with try-catch and try-finally.
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:
- Transferring resource ownership. Keep it local.
java.util.function
doesn't support checked exceptions.
- The Java library not supporting Execute Around.
AutoCloseable::close
declaring that it throws Exception
instead of the being a type parameter of the type.
- Checked exceptions without sum types.
- The Java language syntax in general.