lenientfun

A rewritten version of the Java functional interfaces in order to ignore exception handling in lambdas

License

License

GroupId

GroupId

com.github.mictaege
ArtifactId

ArtifactId

lenientfun
Last Version

Last Version

1.1
Release Date

Release Date

Type

Type

jar
Description

Description

lenientfun
A rewritten version of the Java functional interfaces in order to ignore exception handling in lambdas
Project URL

Project URL

https://github.com/mictaege/lenientfun
Source Code Management

Source Code Management

https://github.com/mictaege/lenientfun

Download lenientfun

How to add to project

<!-- https://jarcasting.com/artifacts/com.github.mictaege/lenientfun/ -->
<dependency>
    <groupId>com.github.mictaege</groupId>
    <artifactId>lenientfun</artifactId>
    <version>1.1</version>
</dependency>
// https://jarcasting.com/artifacts/com.github.mictaege/lenientfun/
implementation 'com.github.mictaege:lenientfun:1.1'
// https://jarcasting.com/artifacts/com.github.mictaege/lenientfun/
implementation ("com.github.mictaege:lenientfun:1.1")
'com.github.mictaege:lenientfun:jar:1.1'
<dependency org="com.github.mictaege" name="lenientfun" rev="1.1">
  <artifact name="lenientfun" type="jar" />
</dependency>
@Grapes(
@Grab(group='com.github.mictaege', module='lenientfun', version='1.1')
)
libraryDependencies += "com.github.mictaege" % "lenientfun" % "1.1"
[com.github.mictaege/lenientfun "1.1"]

Dependencies

test (2)

Group / Artifact Type Version
junit : junit jar 4.12
org.mockito : mockito-core jar 2.2.11

Project Modules

There are no modules declared in this project.

lenientfun

Apache License 2.0 Maven Central Build Status Quality Gate

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'
}

Versions

Version
1.1
1.0