Search code examples
javalambdajava-8classcastexceptionfunctional-interface

Casting Java functional interfaces


As always I was looking through JDK 8 sources and found very interesting code:

@Override
default void forEachRemaining(Consumer<? super Integer> action) {
    if (action instanceof IntConsumer) {
        forEachRemaining((IntConsumer) action);
    } 
}

The question is: how Consumer<? super Integer> could be an instance of IntConsumer? Because they are in different hierarchy.

I have made similar code snippet to test casting:

public class InterfaceExample {
    public static void main(String[] args) {
        IntConsumer intConsumer = i -> { };
        Consumer<Integer> a = (Consumer<Integer>) intConsumer;

        a.accept(123);
    }
}

But it throws ClassCastException:

Exception in thread "main" 
    java.lang.ClassCastException: 
       com.example.InterfaceExample$$Lambda$1/764977973 
     cannot be cast to 
       java.util.function.Consumer

You can find this code at java.util.Spliterator.OfInt#forEachRemaining(java.util.function.Consumer)


Solution

  • Let's see the code below, then you can see why?

    class IntegerConsumer implements Consumer<Integer>, IntConsumer {
       ...
    }
    

    Any class can implement multi-interfaces, one is Consumer<Integer> maybe implements another one is IntConsumer. Sometimes occurs when we want to adapt IntConsumer to Consumer<Integer> and to save its origin type (IntConsumer), then the code looks like as below:

    class IntConsumerAdapter implements Consumer<Integer>, IntConsumer {
    
        @Override
        public void accept(Integer value) {
            accept(value.intValue());
        }
    
        @Override
        public void accept(int value) {
            // todo
        }
    }
    

    Note: it's the usage of Class Adapter Design Pattern.

    THEN you can use IntConsumerAdapter both as Consumer<Integer> and IntConsumer, for example:

    Consumer<? extends Integer> consumer1 = new IntConsumerAdapter();
    IntConsumer consumer2 = new IntConsumerAdapter();
    

    Sink.OfInt is a concrete usage of Class Adapter Design Pattern in jdk-8.The downside of Sink.OfInt#accept(Integer) is clearly that JVM will throw a NullPointerException when it accepts a null value, so that is why Sink is package visible.

    189  interface OfInt extends Sink<Integer>, IntConsumer {
    190 @Override
    191 void accept(int value);
    193 @Override
    194 default void accept(Integer i) {
    195 if (Tripwire.ENABLED)
    196 Tripwire.trip(getClass(), "{0} calling Sink.OfInt.accept(Integer)");
    197 accept(i.intValue());
    198 }
    199 }

    I found it why need to cast a Consumer<Integer> to an IntConsumer if pass a consumer like as IntConsumerAdapter?

    One reason is when we use a Consumer to accept an int the compiler needs to auto-boxing it to an Integer. And in the method accept(Integer) you need to unbox an Integer to an int manually. In the other words, each accept(Integer) does 2 additional operations for boxing/unboxing. It needs to improve the performance so it does some special checking in the algorithm library.

    Another reason is reusing a piece of code. The body of OfInt#forEachRemaining(Consumer) is a good example of applying Adapter Design Pattern for reusing OfInt#forEachRenaming(IntConsumer).

    default void forEachRemaining(Consumer<? super Integer> action) {
        if (action instanceof IntConsumer) {
        //   action's implementation is an example of Class Adapter Design Pattern
        //                                   |
            forEachRemaining((IntConsumer) action);
        }
        else {
        //  method reference expression is an example of Object Adapter Design Pattern
        //                                        |
            forEachRemaining((IntConsumer) action::accept);
        }
    }