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.
It's a neat function which can be called like this:
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:
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:
Showing posts with label exceptions. Show all posts
Showing posts with label exceptions. Show all posts
Wednesday, 13 May 2015
Thursday, 30 April 2015
Cheating with Exceptions - Java 8 Lambdas
Leaving aside the religious debate about Checked vs Runtime exceptions, there are times where due to poorly constructed libraries, dealing with checked examples can drive you insane.
Consider this snippet of code which you might want to write:
For it to compile you need to catch the exception which leaves you with this code:
Although this compiles, the IOException has effectively been swallowed. The user of this method should be informed that an Exception has been thrown.
To address this you could wrap the IOException in a generic RuntimeException as below:
This code does throw an Exception but not the actual IOException which was intended to be thrown by the code. It's possible that those in favour of RuntimeExceptions only would be happy with this code especially if the solution could be refined to created a customised IORuntimeException. Nevertheless the way most people code, they would expect their method to be able to throw the checked IOException from the File.createTempFile method.
The natural way to do this is a little convoluted and looks like this:
From inside the lambda, you would have to catch the IOException, wrap it in a RuntimeException and throw that RuntimeException. The lambda would have to catch the RuntimeException unpack and rethrow the IOException. All very ugly indeed!
In an ideal world what we need is to be able to do is to throw the checked exception from within the lambda without having to change the declaration of computeIfAbsent. In other words, to throw a check exception as if it were an runtime exception. But unfortunately Java doesn't let us do that...
That is not unless we cheat! Here are two methods for doing precisely what we want, throwing a checked exception as if it were a runtime exception.
Method 1 - Using generics:
Note that we have create and thrown an IOException without it being declared in the main method.
Method 2 - Using Unsafe:
Again we have managed to throw an IOException without having declared it in the method.
Whichever method you prefer we are now free to write the original code in this way:
The doThrow() method would obviously be encapsulated in some utility class leaving your code in createTempFileForKey() pretty clean.
Consider this snippet of code which you might want to write:
public void createTempFileForKey(String key) { Map<String, File> tempFiles = new ConcurrentHashMap<>(); //does not compile because it throws an IOException!! tempFiles.computeIfAbsent(key, k -> File.createTempFile(key, ".tmp")); }
For it to compile you need to catch the exception which leaves you with this code:
public void createTempFileForKey(String key) { Map<String, File> tempFiles = new ConcurrentHashMap<>(); tempFiles.computeIfAbsent(key, k -> { try { return File.createTempFile(key, ".tmp"); }catch(IOException e) { e.printStackTrace(); return null; } }); }
Although this compiles, the IOException has effectively been swallowed. The user of this method should be informed that an Exception has been thrown.
To address this you could wrap the IOException in a generic RuntimeException as below:
public void createTempFileForKey(String key) throws RuntimeException { Map<String, File> tempFiles = new ConcurrentHashMap<>(); tempFiles.computeIfAbsent(key, k -> { try { return File.createTempFile(key, ".tmp"); }catch(IOException e) { throw new RuntimeException(e); } }); }
This code does throw an Exception but not the actual IOException which was intended to be thrown by the code. It's possible that those in favour of RuntimeExceptions only would be happy with this code especially if the solution could be refined to created a customised IORuntimeException. Nevertheless the way most people code, they would expect their method to be able to throw the checked IOException from the File.createTempFile method.
The natural way to do this is a little convoluted and looks like this:
public void createTempFileForKey(String key) throws IOException{ Map<String, File> tempFiles = new ConcurrentHashMap<>(); try { tempFiles.computeIfAbsent(key, k -> { try { return File.createTempFile(key, ".tmp"); } catch (IOException e) { throw new RuntimeException(e); } }); }catch(RuntimeException e){ if(e.getCause() instanceof IOException){ throw (IOException)e.getCause(); } } }
From inside the lambda, you would have to catch the IOException, wrap it in a RuntimeException and throw that RuntimeException. The lambda would have to catch the RuntimeException unpack and rethrow the IOException. All very ugly indeed!
In an ideal world what we need is to be able to do is to throw the checked exception from within the lambda without having to change the declaration of computeIfAbsent. In other words, to throw a check exception as if it were an runtime exception. But unfortunately Java doesn't let us do that...
That is not unless we cheat! Here are two methods for doing precisely what we want, throwing a checked exception as if it were a runtime exception.
Method 1 - Using generics:
public static void main(String[] args){ doThrow(new IOException()); } static void doThrow(Exception e) { CheckedException.<RuntimeException> doThrow0(e); } static <E extends Exception> void doThrow0(Exception e) throws E { throw (E) e; }
Note that we have create and thrown an IOException without it being declared in the main method.
Method 2 - Using Unsafe:
public static void main(String[] args){ getUnsafe().throwException(new IOException()); } private static Unsafe getUnsafe(){ try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); return (Unsafe) theUnsafe.get(null); } catch (Exception e) { throw new AssertionError(e); } }
Again we have managed to throw an IOException without having declared it in the method.
Whichever method you prefer we are now free to write the original code in this way:
public void createTempFileForKey(String key) throws IOException{ Map<String, File> tempFiles = new ConcurrentHashMap<>(); tempFiles.computeIfAbsent(key, k -> { try { return File.createTempFile(key, ".tmp"); } catch (IOException e) { throw doThrow(e); } }); } private RuntimeException doThrow(Exception e){ getUnsafe().throwException(e); return new RuntimeException(); } private static Unsafe getUnsafe(){ try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); return (Unsafe) theUnsafe.get(null); } catch (Exception e) { throw new AssertionError(e); } }
The doThrow() method would obviously be encapsulated in some utility class leaving your code in createTempFileForKey() pretty clean.
Subscribe to:
Posts (Atom)