0

I have to define my own throwable functional interfaces, e.g.

@FunctionalInterface
public interface ConsumerEx<T, E extends Exception> {
    public void accept(T t) throws E;

    public default ConsumerEx<T, E> andThen(ConsumerEx<? super T, ? extends E> after) {
        return t -> {
            accept(t);
            after.accept(t);
        };
    }
}

But it cannot work with forEach :-(

auntyellow
  • 2,423
  • 2
  • 20
  • 47

2 Answers2

3

The functional interfaces are general purpose interfaces. But interfaces with generic exceptions only work in a special context where you pass an instance to a method which will invoke the function immediately and re-throw the exception.

However, it doesn’t work in any context where the function is not immediately invoked, e.g. when it is executed in a different thread or just at a later time.

Your special consumer is exactly such a scenario that doesn’t work. You can’t write an ordinary Consumer which delegates to a ConsumerEx instance and catches exactly the exception declared for the particular ConsumerEx instance. Due to type erasure you don’t know the exact type of E. It would be even harder to attempt to wrap the catched exception in a specialized wrapper RuntimeException. The wrapper exception had to be a generic exception with a type argument matching E but you can’t catch a generic exception with type arguments.

These are exactly the same problems the JRE developers would face for parallel stream execution if the functional interfaces allowed generic exception types. It would be impossible for the terminal operation to guaranty that only declared checked generic exceptions are re-thrown.

Holger
  • 285,553
  • 42
  • 434
  • 765
1

If you allow exceptions then this greatly increases the complexity of your code as anything can throw an exception. This makes your code harder to reason about, write and read. Exceptions also break referential transparency when they are caught and an action taken. This has bad consequences and there is a better way.

Given the extra complications Java chose not to allow exceptions in your lambda expressions.

So how does functional programming handle these cases? We need a data structure that has the expected value or contains the exception. These are usually handled by an Either data structure with a left value (the error/exception) or the expected (correct) right value (right as a mnemonic for correct). This is called a right biased Either, as it is expected that the right value contains the correct value. So we need to convert methods that throw exceptions to functions that return an Either.

An example of this is FunctionalJava's Try family of interfaces (https://functionaljava.ci.cloudbees.com/job/master/javadoc/). Taking a consumer example, we reuse the Try0 interface

public interface Try0<A, Z extends Exception> {
    A f() throws Z;
}

and then convert this to a lazy right biased either (fj.data.Validation):

list.forEach(Try.f(() -> methodWithException())._1())

We can either now take action on the exception, or simply ignore it.

Holger
  • 285,553
  • 42
  • 434
  • 765
Mark Perry
  • 1,656
  • 1
  • 9
  • 6