Click here to Skip to main content
15,867,835 members
Articles / Programming Languages / Java

functionExtensions Techniques 1: Throwable Functional Interfaces

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
8 May 2018CPOL10 min read 14.1K   4  
This is the first of three episodes to introduce considerations and techniques used for the throwable functional interfaces defined in my open-sourced library functionExtensions.

functionExtentions is a Java library with Throwable Functional Interfaces, Tuples and Repositories implemented to expedite Functional Programming with JAVA 8. It is released on Maven, with following goals accomplished:

  • Declares a rich set of functional interfaces throwing Exceptions, that can be converted to conventional ones with Checked Exceptions handled with shared exceptionHandlers, thus allow developers to define the concerned business logic only within the lambda expressions.
  • Implements an immutable data structure to keep and retrieve up to first 20 strong-typed values as a set of Tuple classes.
  • Provides Repositories as a kind of Map-based intelligent utility with pre-defined business logic to evaluate a given key or keys (upto 7 strong-typed values as a single Tuple key) to get corresponding value or values (upto 7 strong-typed values as a single Tuple value), buffer and return them if no Exception happened.
  • Multiple powerful generic utilities to support above 3 types utilities, mainly build with Repositories. For example: `Object getNewArray(Class clazz, int length)`, `String deepToString(Object obj)`, `Object copyOfRange(Object array, int from, int to)`, `T convert(Object obj, Class<T> toClass)`, `boolean valueEquals(Object obj1, Object obj2)`, etc. that support various combinations of primitive and objective types and arrays.

This serial of posts include:

  1. Throwable Functional Interfaces
  2. Tuples
  3. Repositories

Introduction

The introduction of Functional Interface and Lambda Expression open the gate to treat business logic as first class members in JAVA 8.

However, the contract of checked Exception handling of almost all Java Functional Interface methods declaration (any codes throwing any Java Exceptions need to be wrapped by try{}catch{}) makes it extremely hard to define the concerned business logic as Lambda Expressions without Exception Handling of boilerplate codes.

This article introduces some techniques, used in my open source project io.github.cruisoring.functionExtensions, to enable JAVA developers focusing on the business logic and handling the exceptions in a more efficient and managable manner.

Background

In Java exceptions under Error and RuntimeException classes are unchecked exceptions, everything else under throwable is checked. If a method has any checked Exception, then it must either handle the exception explicitly or specify the exception using throws keyword.

There is no exemption with Functional Interface in Java 8: if the single abstract method defined doesn't throw some Exception, as those defined in java.util.function package, then the Lambda Expressions must handle any potential exceptions properly before being assigned to any Functional Interface instance.

For example, since Function<T,R> is defined with apply as below:

Java
R apply(T t);

Suppose I want to keep the business logic of converting a String to a Class to getClass variable of Function<String, Class> type, a succinct Lambda Expression as below would always get error of "Unhandled exception: ClassNotFoundException":

Java
Function<String, Class> getClass = className -> <s>Class.forName(className)</s>;

The signature of Class.forName doesn't match the Function<T,R>.apply(T t) that demands you handle any Exceptions accompanied with the concerned business logic.

Java
static Class<?> forName(String className) throws ClassNotFoundException 

Make Functions Throwable

To cope with this inconvenience, changing the corresponding Functional Interface method with "throws Exception" would delay the Exception Handling to some later stage, and make them much more friendly with Lambda Expressions.

Java
@FunctionalInterface
interface FunctionThrowable<T, R> extends AbstractThrowable {
    R apply(T t) throws Exception;

To keep the expected operations reuseable, I would advise you to append "throws Exception" to the single abstract method signature and immediately, you can see Functional Interfaces to be much more friendly. Now assigning Class.forName to a FunctionalThrowable variable works.

Java
FunctionThrowable<String, Class> getClass = Class::forName;

Such simple changes with Functional Interfaces makes Java method much more maintainable and flexible by treating business logic as variables.

I encountered this issue when trying to develop some generic OJDBC based Prepared/CallableStatement execution functions with signature like:

Java
int call(Supplier<Connection> connectionSupplier, String procSignature, Object... args)

Given different types of args, the callableStatement generated from procSignature that has many "?" as placeholders would call different setXXX or registerXXX methods to call the statement and retrieve returned values properly.

The piling of dozens of if-elses presents not only an awkward, but also a rigid code structure that is hard to extend. My first attempt was to define a Functional Interface:

Java
TriConsumer<PreparedStatement, Integer, Object> { void accept(T t, U u, S s) throws Exception;}

Then a map can be defined as below:

Java
public static final Map<Class<?>, TriConsumer<PreparedStatement, 
Integer, Object>> setters = new HashMap<>()
setters.put(LocalDateTime.class, (statement, position, argument) 
	-> statement.setTimestamp(position, Timestamp.valueOf((LocalDateTime) argument)));
setters.put(LocalDate.class, (statement, position, argument) 
	-> statement.setDate(position, Date.valueOf((LocalDate) argument)));
setters.put(boolean.class, (statement, position, argument) 
	-> statement.setBoolean(position, (boolean) argument));
setters.put...;

Enables the following method to handle all kinds of arguments:

Java
private static void setArgument(PreparedStatement ps, int position, Object argument) throws Exception {

    Class clazz = argument.getClass();
    if(argument == null || !setters.containsKey(clazz)){
        defaultSetter.accept(ps, position, argument);
    } else {
        setters.get(clazz).accept(ps, position, argument);
    }
}

Instead of using dozens of if else()..., the main processing flow is quite straight: choose the right business logic for a specific type of argument from a Map, or a default one if it is not defined, then applying it with the relative argument. Although I have delayed the Exception Handling by "throws Exception" with the above method, it is fully possible to use a single try{}catch{} to handle any Exceptions thrown by setTimestamp()/setDate()/setBoolean().

More importantly, without a single line of code changes, it is possible to override the default Lambda for a specific class, or add new methods to handle any other customer classes would change the overall setArgument() behaviours gracefully.

Conversions of Functional Interfaces

Once these throwable functional interfaces are declared, sooner or later, they need to be evaluated and then Exception Handling codes must be provided.

I have tried to convert them as either RunnableThrowable (if there is no value returned) or SupplierThrowable<R> (if a value of type R is returned) with asRunnable(...) or asSupplier(...) as example of converting TriFunctionThrowable<T,U,V,R> to SupplierThrowable<R> as below:

Java
@FunctionalInterface
interface TriFunctionThrowable<T,U,V,R> extends AbstractThrowable {
    R apply(T t, U u, V v) throws Exception;

    default SupplierThrowable<R> asSupplier(T t, U u, V v){
        return () -> apply(t, u, v);
    }
}

Though it works with generic static methods of Functions.java as I would cover later, the functional interfaces without returning values (like ConsumerThrowable<T>, BiConsumerThrowable<T,U>, etc.) can be bundled with a Consumer<Exception> thus converting them gracefully to the Non-Throwable versions. For example:

Java
default BiConsumer<T,U> withHandler(Consumer<Exception> exceptionHandler){
    BiConsumer<T,U> biConsumer = (t, u) -> {
        try {
            accept(t, u);
        } catch (Exception e) {
            if(exceptionHandler != null)
                exceptionHandler.accept(e);
        }
    };
    return biConsumer;
}

For those functional interfaces with value returned (like SupplierThrowable<R>, FunctionThrowable<T,R>, BiFunctionThrowable<T,U,R>, etc., all implementing the empty marker WithValueReturned<R> interface), two methods are defined to convert them to the Non-Throwable counterparts.

Java
Taken BiFunctionThrowable<T, U, R> for instance, following two default methods 
are defined to convert it to FunctionThrowable<T, U, R> (which is defined in java.util.function):
    default BiFunction<T, U, R> withHandler(BiFunction<Exception, 
    WithValueReturned, Object> exceptionHandler)   {
        BiFunction<T, U, R> function = (t, u) -> {
            try {
                return apply(t, u);
            } catch (Exception e) {
                return exceptionHandler == null ? null : (R)exceptionHandler.apply(e, this);
            }
        };
        return function;
    }

    default BiFunction<T,U, R> orElse(R defaultValue){
        BiFunction<T,U, R> function = (t, u) -> {
            try {
                return apply(t, u);
            } catch (Exception e) {
                return defaultValue;
            }
        };
        return function;
    }

The BiFunction<Exception, WithValueReturned, Object> exceptionHandler outlines almost everything needed to handle an Exception thrown by such a throwable functional interface:

  • Exception thrown
  • The lambda expression throwing this Exception

Consequently, before evaluating any of these throwable functional interfaces, a shared BiFunction<Exception, WithValueReturned, R> exceptionHandler can be used to convert it to something similar to a normal Java method with Exception handled by that exceptionHandler instance. For example, it is possible to define a complex static exceptionHandler instance shared by EVERY throwable functional interface:

Java
static Class<? extends Exception>[] negligibles = new Class[]{
        NullPointerException.class, IllegalArgumentException.class
};
static Class<? extends Exception>[] noticeables = new Class[]{
        SQLException.class, NumberFormatException.class
};
static BiFunction<Exception, WithValueReturned, Object> defaultReturner = (ex, lambda) -> {
    final Class<? extends Exception> exceptionType = ex.getClass();
    if(IntStream.range(0, noticeables.length).anyMatch
    (i -> noticeables[i].isAssignableFrom(exceptionType))){
        String msg = ex.getMessage();
        msg = ex.getClass().getSimpleName() + (msg == null?"":":"+msg);
        logs.add(msg);
    }else if(IntStream.range(0, negligibles.length).allMatch
    (i -> !negligibles[i].isAssignableFrom(exceptionType))){
        throw new RuntimeException(ex);
    }
    final Class returnType = TypeHelper.getReturnType(lambda);
    if(returnType == Integer.class || returnType == int.class)
        return -1;
    return TypeHelper.getDefaultValue(returnType);
};

To validate its behaviour:

Java
SupplierThrowable.BiFunctionThrowable<String[], 
Integer, Integer> ff = (sArray, index) -> sArray[index].length();
Integer result = (Integer)customFunctions.apply(ff, new String[]{"a", "ab"}, 1);
assertEquals(Integer.valueOf(2), result);

result = (Integer)customFunctions.apply(ff, new String[]{null, null}, 1);
assertEquals(Integer.valueOf(-1), result);

//Following statement would throw RuntimeException caused by ArrayIndexOutOfBoundsException
//result = (Integer)customFunctions.apply(new String[]{"a", "ab"}, 2, ff);

The NullPointerException or IllegalArgumentExceptions would be neglected, while NumberFormatException would be logged and both cases would return -1 when Exception is caught. Other unexpected Exceptions like ArrayIndexOutOfBoundsException would be re-thrown as RuntimeException and terminate the application.

Then for a simple function accepting 2 arguments and returning an Integer, the concerned business logic would be evaluated with invalid arguments directly after being converted to non-throwable BiFunction<String, Boolean, Integer>:

Java
BiFunctionThrowable<String, Boolean, Integer> f9 = (s, b) -> Integer.valueOf(s) + (b ? 1 : 0);
assertEquals(Integer.valueOf(-1), f9.withHandler(defaultReturner).apply("8.0", true));

The Functions class defined two static instances:

  1. ThrowsRuntimeException: would throw RuntimeException with any Exception thrown by the functional interfaces
  2. ReturnsDefaultValue: would swallow the caught exception silently and return the default value expected from the lambda expression. (0 for WithValueReturned<Integer>, false for WithValueReturned<Boolean>, 0f for WithValueReturned<Float>...)

In this way, the functional interfaces can define the business logic only, leave the handling of exceptional cases to a single method.

Change Exception Handlings Globally

Letting static Functions instances to handle Exceptions also enable the processing flows changed gracefully. Suppose there are many places that execute business logic with the Functions.Default as below, then depending on if the app is switched to RELEASE mode by setting a system property of "isRelease", the following sample method would return default value '0' in RELEASE mode, but throws RuntimeException in DEBUG.

Java
@Test
public void switchingFunctions(){
    String inReleaseMode = (String)Functions.ReturnsDefaultValue.apply
    (s -> System.getProperty((String) s), "isRelease");
    Functions.Default = (inReleaseMode != null) ? 
    Functions.ReturnsDefaultValue : Functions.ThrowsRuntimeException;
    SupplierThrowable.BiFunctionThrowable<String[], 
    Integer, Integer> ff = (sArray, index) -> sArray[index].length();
    Integer result = (Integer) Functions.Default.apply
    (ff, new String[]{"a", "ab"}, 1);
    assertEquals(Integer.valueOf(2), result);

    result = (Integer)Functions.Default.apply(ff, new String[]{null, null}, 1);
    assertEquals(Integer.valueOf(0), result);
}

Lambda with Data

I have come across quite a crooked framework: Caller classes need to invoke business logic of Callee classes that need multiple input parameters. However, the Caller Class instance can only call TestResult execute(Class<?> testClass, String methodName) of Arquillian JUnitTestRunner that accepts only Class of the Callee and name of the called method, no place for extra arguments at all.

First, I managed to keep the input parameters as static variables of Caller classes or Callee classes to allow the Callee instances created by JunitTestRunner to access them to trigger the business logic accordingly. However, if there are multiple Caller classes are created to interact with multiple Callee classes, then invoking business logic that refers to multiple static variables scattered in Caller/Callee classes is a nightmare for code reuse.

Thanks to the data captuable nature of Lambda created by higher-order functions, this issue could be solved with following simplified pseudo codes. First, a Functional Interface is created as below:

Java
@FunctionalInterface
public interface InvokableStep {
    void execute(Callee callee) throws Exception;
}

In the Callee classes, codes like below are created:

Java
private static Stack<InvokableStep> stepStack = new Stack<>();
@Test
public void dontCallMeDirectly() throws Exception {
    InvokableStep step = stepStack.pop();
    Objects.requireNonNull(step);
    step.execute(this);
}

public static void doSomething(JUnitTestRunner junitTestRunner, String param1, String param2)
        throws Throwable {
    Objects.requireNonNull(junitTestRunner);
    InvokableStep step = calleeObj -> {
        Callee callee = (Callee)calleeObj;
        callee.privateMethod(param1);
        callee.privateField = param2;...
    };
    stepStack.push(step);
    junitTestRunner.execute(Callee.class, "dontCallMeDirectly");
}

In the Caller class, now it is possible to invoke something by calling doSomething directly:

  1. The input parameters (param1, param2) are provided directly, in addition to the junitTestRunner directly or indirectly
  2. The InvokableStep instance step is created, accepting one instance of Callee only, consume the input parameters (param1, param2) directly with the given Callee instance that needs to be casted to callee.
  3. The newly created instance step is pushed to the static stack.
  4. Then junitTestRunner trigger the only public instance method dontCallMeDirectly() would get the step instance created in 2), and applied with the Callee instance itself immediately.
  5. Since there is no contention in my case, and everything except Callee instance has been captured by the Lambda instance step, the business logic would be triggered with everything needed.

Well, the process is still quite twisted: the Caller class instance calls the static method of Callee class, which makes the junitTestRunner to create a new Callee instance, and make it execute the expected processes with its own private/public instance resources.

However, you can see it is quite clear that a Lambda Expression could be created with fresh data, thus eliminate many unnecessary variables used to convey the information to execute the business logic.

How to use

The project is hosted at github.com, to include the library, add following info to your maven project:

<dependency>

    <groupId>io.github.cruisoring</groupId>

    <artifactId>functionExtensions</artifactId>

    <version>1.0.1</version>

</dependency>

Following table summarize the 18 throwable Funtional Interfaces:

Throwable Interfaces # of Inputs With Value Returned Non-Throwable function Convert with Handler Convert with defaultValue
RunnableThrowable 0 No Runnable Yes No
SupplierThrowable<R> 0 Yes Supplier<R> Yes Yes
ConsumerThrowable<T> 1 No Consumer<T> Yes No
FunctionThrowable<T,R> 1 Yes Function<T,R> Yes Yes
PredicateThrowable<T> 1 Yes Function<T,Boolean> Yes Yes
BiConsumerThrowable<T,U> 2 No BiConsumer<T,U> Yes No
BiFunctionThrowable<T,U,R> 2 Yes BiFunction<T,U,R> Yes Yes
BiPredicateThrowable<T,U> 2 Yes BiFunction<T,U,Boolean> Yes Yes
TriConsumerThrowable<T,U,V> 3 No TriConsumer<T,U,V> Yes No
TriFunctionThrowable<T,U,V,R> 3 Yes TriFunction<T,U,V,R> Yes Yes
QuadConsumerThrowable<T,U,V,W> 4 No QuadConsumer<T,U,V,W> Yes No
QuadFunctionThrowable<T,U,V,W,R> 4 Yes QuadFunction<T,U,V,W,R> Yes Yes
PentaConsumerThrowable<T,U,V,W,X> 5 No PentaConsumer<T,U,V,W,X> Yes No
PentaFunctionThrowable<T,U,V,W,X,R> 5 Yes PentaFunction<T,U,V,W,X,R> Yes Yes
HexaConsumerThrowable<T,U,V,W,X,Y> 6 No HexaConsumer<T,U,V,W,X,Y> Yes No
HexaFunctionThrowable<T,U,V,W,X,Y,R> 6 Yes HexaFunction<T,U,V,W,X,Y,R> Yes Yes
HeptaConsumerThrowable<T,U,V,W,X,Y,Z> 7 No HeptaConsumer<T,U,V,W,X,Y,Z> Yes No
HeptaFunctionThrowable<T,U,V,W,X,Y,Z,R> 7 Yes HeptaFunction<T,U,V,W,X,Y,Z,R> Yes Yes

Declaring instances is easier than the conventional ones:

BiFunctionThrowable<Integer, String, Integer> biFunctionThrowable = (i, s) -> i + Integer.valueOf(s);

In above example, though Integer.valueOf(s) could NumberFormatException, declaration of doesn't need to handle it.

Then the above biFunctionThrowable can be converted to BiFunction<Integer, String, Integer> with either BiFunction<Exception, WithValueReturned, Object> exceptionHandler or a default value as below:

BiFunction<Integer, String, Integer> biFunction = biFunctionThrowable.withHandler((ex, fun) -> -3);
assertEquals(Integer.valueOf(10), biFunctionThrowable.apply(3, "7"));
assertEquals(Integer.valueOf(-3), biFunction.apply(3, "seven"));
biFunction = biFunctionThrowable.orElse(-1);
assertEquals(Integer.valueOf(-1), biFunction.apply(3, "seven"));

Thanks to the Type Erasure, it is possible to use the same instance of BiFunction<Exception, WithValueReturned, Object> exceptionHandler to execute any throwable Functional Interfaces as ReturnDefaultValue of Functions:

private static BiFunction<Exception, WithValueReturned, Object> returnDefaultValue =
        (Exception ex, WithValueReturned throwable) -> TypeHelper.getDefaultValue(TypeHelper.getReturnType(throwable));

@SuppressWarnings("unchecked")
public static final Functions ReturnsDefaultValue = new Functions(
        ex -> {},
        returnDefaultValue
    );

The TypeHelper.getReturnType(throwable) method would get the returned type of the lambda expression, and TypeHeleper.getDefaultValue(Class) would return system default values that would be introduced in more detail in Repositories.

Consequently, if the evaluation failed with some Exception, default value (0 for Integer in the example) would be returned:

Integer r = (Integer) Functions.ReturnsDefaultValue.apply(s -> Integer.valueOf((String)s), "33.3");
r = (Integer) Functions.ReturnsDefaultValue.apply(f8, "33.3");
Assert.assertEquals(Integer.valueOf(0), r);

Summary

To conclude the techniques discussed in this post:

  • Functional Interface without "throws Exception" forces the business logic to handle Exceptions, that hinder the Functional Programming with Lambda in Java 8.
  • A set of XxxxThrowable Functional Interfaces makes it possible to define only concerned business logic as Lambda variables, that can make the code more modular, reuseable and concise.
  • All Functional Interfaces could be converted to functional interfaces with Exception handled.
  • The overall behaviours of Exception Handling could be switched gracefully with Functions.Default instance for those business logic being executed by it.
  • Finally, an interesting case to use Lambda expression to capture and carry any number of input parameters.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --