12

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.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Will Beason
  • 3,417
  • 2
  • 28
  • 46

4 Answers4

11

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.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
7

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
    }
}
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
3

The problem is that the method accept in the interface Consumer is not declared to throw an exception. Therefore you cannot use a method that throws a checked exception in the lambda.

The solution is to use a for each loop instead.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • 1
    Alas, this is one of the shortcomings of the current lambda implementation that I have banged my head against more than once! :) – pens-fan-69 Apr 24 '15 at 19:51
1

generally we dont write data to file if any exception occures. keeping this in mind we can write our own consumer which will wrap the checked exception into an unchecked exception. this way you could get away with the compile time error. please try below code

import java.io.*;
import java.util.*;
public class HelloWorld{

     public static void main(String []args){
        write(Arrays.asList(1,2,3));
     }

  public static void write(Iterable<?> objects) {
    try (BufferedWriter bw = new BufferedWriter(
        new OutputStreamWriter(
            new FileOutputStream("c:\\out.txt"), "UTF-8"))) {

      objects.forEach(o->myConsumerThrowsRuntimeException(bw,o.toString())); //Unhandled exception type IOException

  } catch (Exception e) {
    //handle exception
  } 
  }
  public static void myConsumerThrowsRuntimeException(Writer writer , String data){
      try{
          writer.write(data);
      }catch(IOException e){

          throw new RuntimeException("Cannot write Data error is "+e.getMessage());
      } 
  } 
}
Shirishkumar Bari
  • 2,692
  • 1
  • 28
  • 36