Exceptions in Lambdas: An Elegant Solution to a Bit of a Mess
Consider the following function for writing to a file:
The idea behind the method is to allow the user to pass in different implementations of InputStream
to the method so that writeToFile
can be called for example with a GZIPOuputStream, SnappyOuputStream
(fast compression) or simply a plain FileInputStream
.
private static void writeToFile(File file, String value, Function<OutputStream, OutputStream> writing) throws IOException{ try (PrintWriter pw = new PrintWriter(new BufferedOutputStream (writing.apply(new FileOutputStream(file))))) { pw.write(value); } }
It’s a neat function which can be called like this:
public static void main(String[] args) { try { //Write with compression //DOES NOT COMPILE!! writeToFile(new File("test"), "Hello World", GZIPOutputStream::new); //Just use the FileOutputStream writeToFile(new File("test"), "Hello World", i->i); }catch(IOException e){ //deal with exception as you choose } }
Unfortunately as pointed out in the comment this does not compile! The reason it doesn’t compile is because the GZIPOutputStream
throws an IOException
in its constructor. What would have been nice was if the IOException was thrown out of the lambda and could then be dealt with in the try catch block – but that’s not how lambdas work :-(
This is in fact how you have to deal with the exception to get the code to compile:
public static void main(String[] args) { try { //Write with compression //COMPILES BUT SO UGLY writeToFile(new File("test"), "Hello World", i -> { try { return new GZIPOutputStream(i); } catch (IOException e) { //HOW ARE WE SUPPOSED TO DEAL WITH THIS EXCEPTION?? throw new AssertionError(e); } }); //Just use the FileOutputStream writeToFile(new File("test"), "Hello World", i->i); }catch(IOException e){ //deal with exception as you choose } }
Not only is this ugly but you are left with the rather awkward problem of what to do with the IOException. In this case we have just re-packaged inside an AssertionError. See my previous post ‘Cheating with Exceptions’ on the correct way to handle this scenario.
But there is a solution to this problem. Rather than using a java.util.function.Function
that takes a value and returns a value, we can create a custom function that takes a value returns a value and throws an Exception. In this way the client code of writeToFile
is nice and clean and can deal with the exception in a natural way. Moreover, lambdas are now used in the way they were intended to make our code prettier and easier to understand.
See full code listing below:
package util; import java.io.*; import java.util.zip.GZIPOutputStream; public class LambdaExceptions { public static void main(String[] args) { try { //Write with compression writeToFile(new File("test"), "Hello World", GZIPOutputStream::new); //Just use the FileOutputStream writeToFile(new File("test"), "Hello World", i->i); }catch(IOException e){ //deal with exception as you choose } } private static void writeToFile(File file, String value, ThrowingFunction<OutputStream, OutputStream, IOException> writing) throws IOException{ try (PrintWriter pw = new PrintWriter(new BufferedOutputStream (writing.apply(new FileOutputStream(file))))) { pw.write(value); } } @FunctionalInterface public interface ThrowingFunction<I, O, T extends Throwable> { O apply(I i) throws T; } }
Reference: | Exceptions in Lambdas: An Elegant Solution to a Bit of a Mess from our JCG partner Daniel Shaya at the Rational Java blog. |