Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consumer<T> that turns checked exceptions into unchecked exceptions reference issue

I have come up with the following code, to be able to pass by consumers that automagically turn checked exceptions into runtime exceptions.

The idea is that the code inside the consumer may throw a checked exception, and if it does so, then it will be converted to a runtime exception.

The interfaces:

@FunctionalInterface
interface WrappingConsumer<T> extends Consumer<T> {
    @Override
    default void accept(final T t) {
        toRuntimeException(() -> wrappingAccept(t));
    }

    void wrappingAccept(final T t) throws Exception;

    static void toRuntimeException(final ExceptionRunnable exceptionRunnable) {
        Objects.requireNonNull(exceptionRunnable);
        try {
            exceptionRunnable.run();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

@FunctionalInterface
interface ExceptionRunnable {
    abstract void run() throws Exception;
}

The testing class:

public class WrappingConsumerProject {
    public static void main(String[] args) {
        WrappingConsumer<Integer> wrappingConsumer = WrappingConsumerProject::test; //Works
        Consumer<Integer> consumer = wrappingConsumer;  //Works

        testConsume(wrappingConsumer);  //Works
        testConsume(consumer);  //Works

        testConsume(WrappingConsumerProject::test); //Fails
        testConsume((Consumer<Integer>)WrappingConsumerProject::test);  //Fails
    }

    private static void test(Integer integer) throws IOException { }

    private static void testConsume(Consumer<Integer> consumer) { }
}

How can it be that some versions just work, and that others give compilation errors?

I am compiling it on Netbeans 8.0, maybe that is of help.

like image 447
skiwi Avatar asked Nov 02 '22 02:11

skiwi


1 Answers

My educated guess: testConsume expects a Consumer<Integer> parameter:

private static void testConsume(Consumer<Integer> consumer) { }

So when the compiler sees this:

testConsume(WrappingConsumerProject::test); //Fails

it of course can't tell that you want to interpret the method reference as a WrappingConsumer<Integer>, and you haven't told the compiler anything that would help it believe this is supposed to be a WrappingConsumer<Integer>. (Obviously looking at the name WrappingConsumerProject isn't enough of a hint. :-) So it has to try to interpret WrappingConsumerProject::test as a Consumer<Integer>, which means it's equivalent to

testConsume(new Consumer<Integer> () {
    public void accept(Integer t) {
        WrappingConsumerProject.test(t);
    }
}

which fails because IOException isn't handled. On the other hand, if it knew that the interface was a WrappingConsumer<Integer>, it would handle it correctly, because then it would interpret the method interface as part of the implementation of wrappingAccept(), rather than accept(). Thus:

WrappingConsumer<Integer> wrappingConsumer = WrappingConsumerProject::test;

is equivalent to

WrappingConsumer<Integer> wrappingConsumer = new WrappingConsumerProject<Integer>() {
    public void wrappingAccept(Integer t) throws Exception {
        WrappingConsumerProject.test(t);
    }
}

which is OK since wrappingAccept() has a throws clause. When this new object is treated as a Consumer<Integer>, it's still OK because when the accept() method is called:

Consumer<Integer> consumer = wrappingConsumer;

// then somebody calls
consumer.accept();

it calls the default you set up; it doesn't call WrappingConsumerProject.test directly. That fact was already determined when wrappingConsumer was declared. Casting it to Consumer<Integer> doesn't change that fact.

But that's not the case with, say

Consumer<Integer> consumer2 = WrappingConsumerProject::test;

or

testConsume(WrappingConsumerProject::test); //Fails

In those cases, it has to assume WrappingConsumerProject::test is the implementation of accept(), because it has to interpret it as a Consumer<Integer>. Nothing tells it to interpret it as a WrappingConsumer<Integer>, as explained above. Thus if somebody called

consumer2.accept();

it would try to call WrappingConsumerProject::test directly, without going through your default.

Anyway, although I'm not an expert on the nuances of type inference or other JLS rules, this makes sense to me.

like image 186
ajb Avatar answered Nov 15 '22 04:11

ajb