lenientfun
In order to delegate the burden of exception handling from an API user to an API provider, this is a rewritten version of the Java functional interfaces with checked exceptions signatures.
In Java 8 lambda expressions and method references has been introduced in order to facilitate a functional programming style. This way a lot of problems could be solved in a far more short and concise way. But problems arise if exception handling comes into play and readability is quickly lost. The main reason for this is that Javas functional interfaces - such as Function, Consumer, Supplier etc. - does not declare any exception in their signatures, which leads to a situation that
- all checked exceptions has to be handled inside the lambda expression
- no method reference could be used for a method that declares a checked exception
Problem
Assumed that we have a PersonConverter that is able to convert Persons into any String using a given Function:
public String convert(final Person person, final Function<Person, String> converter) {
return converter.apply(person);
}
Now we are able to convert Persons into any format we want to:
final String json = converter.convert(person, p -> "{\n" +
" \"firstName\": \"" + p.getFirstName() + "\",\n" +
" \"surName\": \"" + p.getSurName() + "\"\n" +
"}");
final String yaml = converter.convert(person, p ->
"firstName: " + p.getFirstName() + "\n" +
"surName: " + p.getSurName() + "");
That's cool, isn't it?
But things are different if exception handling comes into play. If for example our converted formats should also contain a birthday and the birthdays accessor declares an checked exception our code may look like follows:
final String json = converter.convert(person, p -> {
try {
return "{\n" +
" \"firstName\": \"" + p.getFirstName() + "\",\n" +
" \"surName\": \"" + p.getSurName() + "\",\n" +
" \"birthDay\": \"" + p.formatedBirthday() + "\"\n" + // throws checked exception
"}";
} catch (final Exception e) {
logger.error(e);
throw new IllegalArgumentException("Conversion fails", e);
}
});
final String yaml = converter.convert(person, p -> {
try {
return "firstName: " + p.getFirstName() + "\n" +
"surName: " + p.getSurName() + "\n" +
"birthDay: " + p.formatedBirthday() + ""; // throws checked exception
} catch (final Exception e) {
logger.error(e);
throw new IllegalArgumentException("Conversion fails", e);
}
});
There are several problems with this code. First of all the exception handling has to be done inside the lambda expression and this makes the code less readable. Furthermore the lambda expressions seems not to be the right places to handle the exceptions at all. Wouldn't it be more reasonable if the PersonConverter alone is responsible for all the exception handling stuff like logging etc.?
Solution
With lenientfun the problems described above could be avoided.
First we rewrite the PersonConverter and change the Java Function into a LenientFunction which is aware of checked exceptions:
public String convert(final Person person, final LenientFunction<Person, String> converter) {
try {
return converter.apply(person);
} catch (final Exception e) {
logger.error(e);
throw new IllegalArgumentException("Conversion fails", e);
}
}
Now the PersonConverter is responsible for all the exception handling stuff and we got rid of the exception handling inside the lambda expression:
final String json = converter.convert(person, p -> "{\n" +
" \"firstName\": \"" + p.getFirstName() + "\",\n" +
" \"surName\": \"" + p.getSurName() + "\",\n" +
" \"birthDay\": \"" + p.formatedBirthday() + "\"\n" + // throws checked exception
"}");
final String yaml = converter.convert(person, p ->
"firstName: " + p.getFirstName() + "\n" +
"surName: " + p.getSurName() + "\n" +
"birthDay: " + p.formatedBirthday() + ""); // throws checked exception
Conclusion
-
For every functional interface of the java.util.function package lenientfun provides a "lenient" version which is aware of checked exceptions.
-
This "lenient" functional interfaces could be used to design own API's
-
For API's that are using the Java functional interfaces there is a LenientAdapter that could be used to adapt the "lenient" style:
final String json = converter.convert(person, LenientAdapter.func(p -> "{\n" + // converter expects java.util.function.Function
" \"firstName\": \"" + p.getFirstName() + "\",\n" +
" \"surName\": \"" + p.getSurName() + "\",\n" +
" \"birthDay\": \"" + p.formatedBirthday() + "\"\n" + // throws checked exception
"}"));
Installation
From Maven Central with the following artifact coordinates
Maven
<dependency>
<groupId>com.github.mictaege</groupId>
<artifactId>lenientfun</artifactId>
<version>x.x</version>
</dependency>
Gradle
dependencies {
compile 'com.github.mictaege:lenientfun:x.x'
}