Handling exceptions functional style
Java supports checked exceptions from the very start. With Java 8 the language element lambda and the RT library modifications supporting stream operations introduced functional programming style to the language. Functional style and exceptions are not really good friends. In this article, I will describe a simple library that handles exceptions somehow similar to how null
is handled using Optional
.
The library works (after all it is a single Class and some inner classes, but really not many). On the other hand, I am not absolutely sure that using the library will not deteriorate the programming style of the average programmer. It may happen that someone having a hammer sees everything as a nail. A hammer is not a good pedicure tool. Have a look at this library more like an idea and not as a final tool that tells you how to create perfect code handling exceptions.
Also, come and listen to the presentation of Michael Feathers about exceptions May 6, 2019, Zürich https://www.jug.ch/html/events/2019/exceptions.html
1. Handling Checked Exception
Checked exceptions have to be declared or caught like a cold. This is a major difference from null
. Evaluating an expression can silently be null
but it cannot silently throw a checked exception. When the result is null
then we may use that to signal that there is no value or we can check that and use a "default" value instead of null
. The code pattern doing that is
var x = expression;
if( expression == null ){
x = default expression that is really never null
}
The pattern topology is the same in case the evaluation of the expression can throw a checked exception, although the Java syntax is a bit different:
Type x; // you cannot use 'var' here
try{
x = expression
}catch(Exception weHardlyEverUseThisValue){
x = default expression that does not throw exception
}
The structure can be more complex if the second expression can also be null
or may throw an exception and we need a third expression or even more expressions to evaluate in case the former ones failed. This is especially naughty in case of an exception throwing expression because of the many bracketing
Type x; // you cannot use 'var' here
try{
try {
x = expression1
}catch(Exception e){
try {
x = expression2
}catch(Exception e){
try {
x = expression3
}catch(Exception e){
x = expression4
}}}}catch(Exception e){
x = default expression that does not throw exception
}
In the case of null
handling, we have Optional
. It is not perfect to fix the million dollar problem, which is the name of designing a language having null
and also an underestimation, but it makes life a bit better if used well. (And much worse if used in the wrong way, which you are free to say that what I describe in this article is exactly that.)
In the case of null
resulting expressions, you can write
var x = Optional.ofNullable(expresssion)
.orElse(default expression that is nere null);
You can also write
var x = Optional.ofNullable(expresssion1)
.or( () -> Optional.ofNullable(expression2))
.or( () -> Optional.ofNullable(expression3))
.or( () -> Optional.ofNullable(expression4))
...
.orElse(default expression that is nere null);
when you have many alternatives for the value. But you cannot do the same thing in case the expression throws an exception. Or can you?
2. Exceptional
The library Exceptional
(https://github.com/verhas/exceptional)
<groupId>com.javax0</groupId>
<artifactId>exceptional</artifactId>
<version>1.0.0</version>
implements all the methods that are implemented in Optional
, one method more and some of the methods a bit differently aiming to be used the same way in case of exceptions as was depicted above for Optional
in case of null
values.
You can create an Exceptional
value using Exceptional.of()
or Exceptional.ofNullable()
. The important difference is that the argument is not the value but rather a supplier that provides the value. This supplier is not the JDK Supplier
because that one cannot throw an exception and that way the whole library would be useless. This supplier has to be Exceptional.ThrowingSupplier
which is exactly the same as the JDK Supplier
but the method get()
may throw an Exception
. (Also note that only an Exception
and not Throwable
which you should only catch as often as you catch a red-hot iron ball using bare hands.)
What you can write in this case is
var x = Exceptional.of(() -> expression) // you CAN use 'var' here
.orElse(default expression that does not throw exception);
It is shorter and shorter is usually more readable. (Or not? That is why APL is so popular? Or is it? What is APL you ask?)
If you have multiple alternatives you can write
var x = Exceptional.of(() -> expression1) // you CAN use 'var' here
.or(() -> expression2)
.or(() -> expression3) // these are also ThrowingSupplier expressions
.or(() -> expression4)
...
.orElse(default expression that does not throw exception);
In case some of the suppliers may result null
not only throwing an exception there are ofNullable()
and orNullable()
variants of the methods. (The orNullable()
does not exist in Optional
but here it makes sense if the whole library does at all.)
If you are familiar with Optional
and use the more advanced methods like ifPresent()
, ifPresentOrElse()
, orElseThrow()
, stream()
, map()
, flatMap()
, filter()
then it will not be difficult to use Exceptional
. Similar methods with the same name exist in the class. The difference again is that in case the argument for the method in Optional
is a Function
then it is ThrowingFunction
in case of Exceptional
. Using that possibility you can write code like
private int getEvenAfterOdd(int i) throws Exception {
if( i % 2 == 0 ){
throw new Exception();
}
return 1;
}
@Test
@DisplayName("some odd example")
void testToString() {
Assertions.assertEquals("1",
Exceptional.of(() -> getEvenAfterOdd(1))
.map(i -> getEvenAfterOdd(i+1))
.or( () -> getEvenAfterOdd(1))
.map(i -> i.toString()).orElse("something")
);
}
It is also possible to handle the exceptions in functional expressions like in the following example:
private int getEvenAfterOdd(int i) throws Exception {
if (i % 2 == 0) {
throw new Exception();
}
return 1;
}
@Test
void avoidExceptionsForSuppliers() {
Assertions.assertEquals(14,
(int) Optional.of(13).map(i ->
Exceptional.of(() -> inc(i))
.orElse(0)).orElse(15));
}
Last, but not least you can mimic the ?.
operator of Groovy writing
a.b.c.d.e.f
expressions, where all the variables/fields may be null
and accessing the next field through them, causes NPE. You can, however, write
var x = Exceptional.ofNullable( () -> a.b.c.d.e.f).orElse(null);
3. Summary
Remember what I told you about the hammer. Use with care and for the greater good and other BS.
Comments
Please leave your comments using Disqus, or just press one of the happy faces. If for any reason you do not want to leave a comment here, you can still create a Github ticket.