Using options with threads in Java

I tried to reorganize the old code to use streams, and my first approach:

public void run() throws IOException { Files.list(this.source) .filter(Images::isImage) .map(Image::new) .filter(image -> image.isProportional(this.height, this.width)) .map(image -> image.resize(this.height, this.width)) .forEach(image -> Images.write(image, this.destination)); } 

This is not compilation, as the new Image () and Images.write () call IOExceptions.

Acquiring these exceptions with UncheckedIOException will not lead to a trick, since I do not want to stop processing other images if one of them does not work.

So, I finished writing 2 private methods:

 private Optional<Image> createImage(Path imagePath) { try { return Optional.of(new Image(imagePath)); } catch (IOException e) { return Optional.empty(); } } private void write(Image image) { try { Images.write(image, this.destination); } catch (IOException e) { // log error } } 

createImage () returns optional as it seems reasonable. However, after that, my code became really ugly:

 public void run() throws IOException { Files.list(source) .filter(Images::isImage) .map(this::createImage) .filter(image -> image.isPresent() && image.get().isProportional(this.height, this.width)) .map(image -> image.get().resize(this.height, this.width)) .forEach(this::write); } 

Is there a way to avoid using get () and isPresent () in this code?

Thanks!

+7
java java-8 java-stream optional
source share
2 answers

One of the nice things about Optionals is that filtering, matching, and flat mapping functions are only started on them when the :: isPresent option is true, so:

 public void run() throws IOException { Files.list(source) .filter(Images::isImage) .map(this::createImage) // turns every non-proportional Optional<Image> into empty optionals .map(image -> image.filter(i -> i.isProportional(this.height, this.width))) // resizes every proportional Optional<Image>, while doing nothing on the empties .map(image -> image.map(i -> i.resize(this.height, this.width))) // applies the writing consumer for each non-empty Optional<Image> .forEach(image -> image.ifPresent(this::write)); } 

Another way is to call only the :: isPresent and Optional :: get options in separate stream transformations:

 public void run() throws IOException { Files.list(source) .filter(Images::isImage) .map(this::createImage) // filter out the empty optionals .filter(Optional::isPresent) // replace every optional with its contained value .map(Optional::get) .filter(image -> image.isProportional(this.height, this.width)) .map(image -> image.resize(this.height, this.width)) .forEach(this::write); } 

Another way (which I refuse to recommend as the main solution due to its relative oddity) is to change the way you create a static image into a stream generator instead of an optional generator to take advantage of flatMap:

 private Stream<Image> createImage(Path imagePath) { try { return Stream.of(new Image(imagePath)); } catch (IOException e) { return Stream.empty(); } } public void run() throws IOException { Files.list(source) .filter(Images::isImage) // inserts into the stream the resulting image (empty streams are handled seamlessly) .flatMap(this::createImage) .filter(image -> image.isProportional(this.height, this.width)) .map(image -> image.resize(this.height, this.width)) .forEach(this::write); } 

Second, go to this solution; this seems simpler, and since the static method is private, in any case there will be less screaming from end users, other developers and random people having access to decent Java 8 decompilers ( http://www.benf.org/other/cfr/ )

+16
source share

Starting with Java9, you can use flatMap and Optional::stream to filter empty flatMap :

 public void run() throws IOException { Files.list(source) .filter(Images::isImage) .map(this::createImage) .flatMap(Optional::stream) .filter(image -> image.isProportional(this.height, this.width)) .map(image -> image.resize(this.height, this.width)) .forEach(this::write); } 
0
source share

All Articles