I am playing around with ways to write a set of objects to a file. Why does the below implementation using Iterable.forEach() not compile? In Eclipse, I get the message that an IOException is not being handled. This is particularly confusing since I do appear to be handling IOExceptions.
public void write(Iterable<?> objects) {
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("out.txt"), "UTF-8"));) {
objects.forEach((o) -> bw.write(o.toString())); //Unhandled exception type IOException
} catch (IOException e) {
//handle exception
}
}
Obviously, the below works. I'm interested in why the above doesn't work and how to fix it.
for (Object o : objects) { bw.write(o.toString()); }
I've checked the Consumer and Iterable documentation, and neither of them seem to suggest how to solve this.
IOException is a Java exception that occurs when an IO operation fails. Develop can explicitly handle the exception in a try-catch-finally block and print out the root cause of the failure. The developer can take the correct actions to solve this situation by having additional code in the catch and finally blocks.
forEach. Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception. Unless otherwise specified by the implementing class, actions are performed in the order of iteration (if an iteration order is specified).
How to Handle IOException in Java? We can handle IOException by using the try and catch blocks to handle the exception.
Precursor: The Catch or Specify Requirement.
Let's write the lambda as an anonymous class:
objects.forEach(new Consumer<Object>() {
public void accept(Object o) {
bw.write(o.toString());
}
});
Are we handling it? It should be clear that we are not.
When we write a lambda expression, we are declaring the body of a method. We also can't declare a throws
clause for a lambda.
The only "way around it" would be to do something like the following:
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("out.txt"), "UTF-8"));) {
objects.forEach((o) -> {
try {
bw.write(o.toString()));
} catch(IOException kludgy) {
throw new UncheckedIOException(kludgy);
}
});
} catch (UncheckedIOException kludgy) {
IOException cause = kludgy.getCause();
// handle exception
}
(See also UncheckedIOException
.)
Iterable.forEach
guarantees that wrapping and throwing the exception like in that example works:
Exceptions thrown by the action are relayed to the caller.
However, it would be better to simply avoid using forEach
in a context that throws a checked exception, or catch and handle the exception in the body of the lambda.
If all you're concerned about is printing strings to a file, use a PrintStream
or perhaps PrintWriter
instead of the other Writer
classes. The notable feature of PrintStream
and PrintWriter
is that their printing operations don't throw IOException
. They also call toString
on objects automatically, which makes things very convenient:
public void write1(Iterable<?> objects) {
try (PrintStream ps = new PrintStream("printout.txt", "UTF-8")) {
objects.forEach(ps::println);
} catch (IOException ioe) {
// handle
}
}
If you're concerned about errors, you can call PrintStream.checkError
, although this doesn't tell you any specifics about any error that might have occurred.
The general question still stands, though, about what to do if you want to call an exception-throwing method from within a context (such as forEach
) that doesn't permit it. This is only annoying to deal with, though only moderately so. It does require some setup, however. Suppose we want to write a Consumer
that throws an IOException
. We have to declare our own functional interface:
interface IOConsumer<T> {
void accept(T t) throws IOException;
}
Now we need to write a function that converts an IOConsumer
to a Consumer
. It does this by converting any IOException
it catches into an UncheckedIOException
, an exception created for this purpose.
static <T> Consumer<T> wrap(IOConsumer<? super T> ioc) {
return t -> {
try {
ioc.accept(t);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
};
}
With these in place, we can now rewrite the original example as follows:
public void write2(Iterable<?> objects) {
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("out.txt"), "UTF-8"))) {
objects.forEach(wrap(o -> bw.write(o.toString())));
} catch (IOException|UncheckedIOException e) {
//handle exception
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With