-1

I am trying to write a stream of custom objects with a nice toString method to a file, line by line. What I have so far is this:

private static void writeToFile(Set<Article> articles, Charset cs, Path path) throws IOException {

    try(Writer w = 
            new BufferedWriter(
            new OutputStreamWriter(
            new FileOutputStream(path.toFile()), cs))) {

        articles.stream().map(Article::toString).forEach(w::write);

    } catch(IOException e) {}

I am getting the error Unhandled IOException on w::write, which is strange because I am catching this Exception?

Other point, is it possible to write those objects line by line?

zwiebl
  • 685
  • 2
  • 11
  • 24
  • Possible duplicate of [Java 8: How do I work with exception throwing methods in streams?](http://stackoverflow.com/questions/23548589/java-8-how-do-i-work-with-exception-throwing-methods-in-streams) – Natecat Dec 25 '16 at 00:47
  • They are actually very close, but the similarity is hidden. – HTNW Dec 25 '16 at 00:50
  • @zwiebl It gives you the exact solution you need, you need to wrap the method call in the `forEach` in another method that catches the error, the outside try/catch won't catch an exception inside the forEach, just like his `throws` won't deal with his `Exception`. – Natecat Dec 25 '16 at 00:50
  • what about the new line for every new object from the stream? – zwiebl Dec 25 '16 at 00:52
  • @zwiebl Don't ask compound questions. And of course, add a newline(`\n`) after each string. – Natecat Dec 25 '16 at 00:53
  • this will create an extra line at the end... – zwiebl Dec 25 '16 at 00:53
  • Note that on *nix systems said trailing newline is the standard. – HTNW Dec 25 '16 at 01:03

2 Answers2

4

PrintWriter is useful for cases such as this, as its methods don't throw IOException. It also has print(Object) overloads, which converting the object to a string via its toString method. (Really it calls String.valueOf which handles nulls before delegating to toString.)

Unfortunately PrintWriter is a somewhat old-fashioned API, as it takes a File instead of a Path and a charset name instead of an actual Charset object. But converting isn't too big a deal.

Since no intermediate map call is necessary to convert the object to a string, it's sufficient to call forEach directly on the collection.

Here's the resulting code:

static void writeToFile(Set<Article> articles, Charset cs, Path path) throws IOException {
    try (PrintWriter pw = new PrintWriter(path.toFile(), cs.name())) {
        articles.forEach(pw::println);
    }
}
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
2

foreach expects a Consumer<? super E>, where E is the type of the Stream's elements. The signature of Consumer<T>::accept is

void accept(T t)

Note how it does not throw any checked exceptions.

Now, Writer::write does throw a checked IOException, so when you do .foreach(w::write), the compiler complains because you are trying to use a method that throws an exception (Writer::write), as one that doesn't (Consumer<T>::accept). In order to fix this, you have to handle the exception in the foreach itself:

articles.stream().map(Article::toString).forEach(str -> {
    try {
        w.write(str + System.lineSeparator()); // Add a newline to each string.
    } catch(IOException e) {
        ...
    }
});

Note that instead of writing from the stream, you can construct the entire String in memory and then write it all at once.

String data = articles.stream().map(Article::toString).map(str -> str + System.lineSeparator()).reduce("", (acc, str) -> str + acc)
w.write(data.substring(0, data.length()-1)); // Drop last newline
HTNW
  • 27,182
  • 1
  • 32
  • 60