Avoid Exceptions in Streams

 class​ LogBooks {
 
 static​ List<LogBook> getAll() ​throws​ IOException {
»return​ Files.walk(Paths.get(​"/var/log"​))
  .filter(Files::isRegularFile)
  .filter(LogBook::isLogbook)
  .map(path -> {
 try​ {
 return​ ​new​ LogBook(path);
  } ​catch​ (IOException e) {
»throw​ ​new​ UncheckedIOException(e);
  }
  })
  .collect(Collectors.toList());
  }
 }

As you’ve seen in Chapter 5, Prepare for Things Going Wrong, you should be prepared for dealing with exceptions. Unfortunately, this can be hard with lambda expressions.

In the code above, we use the Java NIO API to iterate over the filesystem with a stream. Files.walk() opens a Stream<Path> that starts from a given Path and contains all files and directories below that Path.

But when you work with the filesystem, there’s always a chance of triggering an IOException. For example, files could be on an external drive that got disconnected, or directories could be deleted by a different process at runtime.

By design streams don’t go well with checked exceptions. You have to handle them inside the stream. That’s why we catch the IOException in the map() operation and convert it to an UncheckedIOException, which extends RuntimeException.

That’s a way to get the whole expression to compile, but it’s not beautiful. There’s just no proper mechanism to handle exceptions, even unchecked ones, within the functional style in Java. Essentially, this comes from a mismatch of paradigms. Functions work on inputs and produce outputs. Functions don’t throw (or catch) exceptions.

So how can we deal with exceptions without breaking out of the functional programming style here?

 class​ LogBooks {
 
 static​ List<LogBook> getAll() ​throws​ IOException {
»try​ (Stream<Path> stream = Files.walk(Paths.get(​"/var/log"​))) {
 return​ stream.filter(Files::isRegularFile)
  .filter(LogBook::isLogbook)
  .flatMap(path -> {
 try​ {
 return​ Stream.of(​new​ LogBook(path));
  } ​catch​ (IOException e) {
»return​ Stream.empty();
  }
  })
  .collect(Collectors.toList());
  }
  }
 }

As you can see, we still have a try-catch block in the code above. There’s no way around this, but we no longer convert the checked exception to an unchecked one. Instead, we simply remove the exceptional element from the stream.

To do so, we used the flatMap() operator. This one’s similar to map(), but instead of mapping a type to another one, it maps a type to a Stream of another type. If everything goes right, we simply create a new stream for a single element with Stream.of(element). If there are problems, we just return Stream.empty().

This matches the paradigm of the functional style much better. No matter what, an exception won’t take down the entire computation, and the stream produces an output based on its inputs. This also makes reasoning about (and documenting) that code more straightforward. Of course, you can also handle exceptions by logging, but keep in mind that this easily causes side effects, violating the pure functional paradigm.

In a nutshell: You’re better off in the functional style if you avoid exceptions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.60.62