Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle IOException in Iterable.forEach?

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.

like image 781
Will Beason Avatar asked Apr 24 '15 19:04

Will Beason


People also ask

How do you deal with IOException?

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.

What does the forEach method on Iterable do?

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).

What can I do with IOException in Java?

How to Handle IOException in Java? We can handle IOException by using the try and catch blocks to handle the exception.


2 Answers

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.

like image 155
Radiodef Avatar answered Sep 21 '22 14:09

Radiodef


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
    }
}
like image 35
Stuart Marks Avatar answered Sep 19 '22 14:09

Stuart Marks