6

I'm trying to write to a file using Java Streams. To my knowledge you don't have to catch an exception as long as you throw it forward. The compiler however throws an error (on the line of the stream.foreach()...) and says

error: unreported exception IOException; must be caught or declared to be thrown

Could someone explain why this is? (I'm only interested in solutions using streams)

public void save(String file, ArrayList<String> text) throws IOException {
    FileWriter fw = new FileWriter(file);
    text.stream().forEach(x->fw.write(x +"\n"));
    fw.close();
}

Netbeans suggests this, but is there a shorter way?

public void save(String file, ArrayList<String> text) throws IOException {
    FileWriter fw = new FileWriter(file);
    text.stream().forEach(x->{
        try {
            fw.write(x +"\n");
        } catch (IOException ex) {
            ...
        }
    });
    fw.close();
}
user2864740
  • 60,010
  • 15
  • 145
  • 220
Jonatan
  • 1,182
  • 8
  • 20
  • 1
    It is a very annoying problem, and I don't think there is a shorter solution. See [here](http://stackoverflow.com/q/23548589) and [here](http://stackoverflow.com/q/27644361) and [here](http://stackoverflow.com/q/19757300) –  Aug 23 '16 at 00:09
  • yeah my answer basically contains friendly dogs' answer. You can't ignore anything but `RuntimeExeptions` – innuendomaximus Aug 23 '16 at 00:14
  • @Thorvason That's incorrect. You can also ignore subclasses of `Error`. See https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html – sprinter Aug 23 '16 at 05:28

2 Answers2

2

Unfortunately there's no easy solution to this. Lambda expressions that throw checked exceptions need to be caught. Probably the neatest way to do this is to convert the checked exception to an unchecked exception in a separate method.

try {
    text.forEach(this::write);
catch (UncheckedIOException ex) {
    ...
}

private void write(String line) throws UncheckedIOException {
    try {
        fw.write(line +"\n");
    } catch (IOException ex) {
        throw new UncheckedIOException(ex);
    }
});

Though, to be honest, this is not really any neater than NetBean's suggestion other than making your save method cleaner.

If you are unclear on the checked/unchecked exception distinction then read the accepted answer here.

Note that List has a forEach method (actually Iterable that List extends) so stream() is redundant.

Community
  • 1
  • 1
sprinter
  • 27,148
  • 6
  • 47
  • 78
  • The `UncheckedIOException` should include the original cause: `throw new UncheckedIOException(ex);`. Alternatively, you could just `throw ex;` to simply propagate the exception up the call stack. This later choice seems reasonable in this situation since `save()` is already declared with `throws IOException`. – Code-Apprentice Aug 23 '16 at 00:24
  • @Code-Apprentice thanks - good catch. Do you mean `throw ex` inside `write`? That won't work because it throw an unchecked and ex is checked. – sprinter Aug 23 '16 at 00:26
  • After originally posting that comment, I edited it several times. Not sure if you saw the most recent version. I think `throw ex;` is preferable in this case since `save()` already has an agreeable `throws` clause. – Code-Apprentice Aug 23 '16 at 00:27
  • The way I read the OP, the original problem stems from the lambda expression, not from `throws` clauses in general. – Code-Apprentice Aug 23 '16 at 00:28
1

The forEach action expects a Consumer whose accept method does not allow to throw checked exceptions. In your case, the solution is simple:

public void save(String file, ArrayList<String> text) throws IOException {
    Files.write(Paths.get(file), text, Charset.defaultCharset());
}

If it really has to be a stream operation, you could use

public void save(String file, ArrayList<String> text) throws IOException {
    Files.write(Paths.get(file),
                (Iterable<String>)()->text.stream().iterator(), Charset.defaultCharset());
}

writing it this way works as the stream operation does not throw checked exceptions, instead, the outer operation Files.write may.

A general solution for dealing with checked exceptions in a Consumer could be a helper method performing the wrapping and unwrapping:

interface IOConsumer<T> extends Consumer<T> {
    public default void accept(T t) {
        try { doIO(t); } catch(IOException ex) { throw new UncheckedIOException(ex); }
    }
    void doIO(T t) throws IOException;

    public static <E> void doForEach(Stream<? extends E> s, IOConsumer<? super E> c)
    throws IOException {
        try{ s.forEachOrdered(c); } catch(UncheckedIOException ex){ throw ex.getCause(); }
    }
}

which you can use like

public void save(String file, ArrayList<String> text) throws IOException {
    try( FileWriter fw = new FileWriter(file) ) {
        IOConsumer.doForEach(text.stream(), x -> fw.write(x +"\n"));
    }
}

This construct ensures that you properly handle potential IOExceptions at the initiator level. Note that I replaced your unsafe explicit close() call with the proper try-with-resource construct.

With this, you still can use arbitrary intermediate stream operations, i.e. doForEach(text.stream().intermediateOp1().iOp2(), x -> fw.write(x +"\n")) or stream sources other than collections.

Holger
  • 285,553
  • 42
  • 434
  • 765