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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With