Casting Java functional interfaces
Asked Answered
R

3

28

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)

Retaretable answered 31/5, 2017 at 15:27 Comment(0)
F
23

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);
    }
}
Flibbertigibbet answered 31/5, 2017 at 15:33 Comment(2)
That interface already has "forEachRemaining" that takes IntConsumer,how is it possible that "forEachRemaining(Consumer<? super Integer> action)" will be called with object that implements IntConsumer? Unless caller specially casting it to Consumer, but why?Cruz
@Cruz because IntStream is not a Consumer<Integer>. they are in different class hierarchies. but you can use an adapter pattern adapts a Consumer<Integer> to a IntConsumer in this case.Flibbertigibbet
T
21

Because the implementing class might implement both interfaces.

It is legal to cast any type to any interface type, as long as the object being passed might implement the destination interface. This is known at compile-time to be false when the source type is a final class that does not implement the interface, or when it can be proven to have different type parameterization that results in the same erasure. At run-time, if the object does not implement the interface, you'll get a ClassCastException. Checking with instanceof before attempting to cast allows you to avoid the exception.

From the Java Language Specification, 5.5.1: Reference Type Casting:

5.5.1 Reference Type Casting Given a compile-time reference type S (source) and a compile-time reference type T (target), a casting conversion exists from S to T if no compile-time errors occur due to the following rules.

...

• If T is an interface type: – If S is not a final class (§8.1.1), then, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types, and that the erasures of X and Y are the same, a compile-time error occurs.

Otherwise, the cast is always legal at compile time (because even if S does not implement T, a subclass of S might).

Thurlough answered 31/5, 2017 at 15:38 Comment(3)
Thank you. And what happens in case of lambda? It just infers type from the right side.Retaretable
Right, with one of the key points being that an object of a functional interface type does not necessarily correspond to a lambda expression.Previse
@AndriiAbramov, the object resulting from a lambda expression of inferred type Consumer<? super Integer> will not be an instance of IntConsumer. The instanceof expression in the method you presented will evaluate to false in that case, with the result that the method has no observable effect.Previse
R
10

Note that you could have found this behavior without looking into the source code, just by looking at the official API documentation, you have linked yourself:

Implementation Requirements:

If the action is an instance of IntConsumer then it is cast to IntConsumer and passed to forEachRemaining(java.util.function.IntConsumer); otherwise the action is adapted to an instance of IntConsumer, by boxing the argument of IntConsumer, and then passed to forEachRemaining(java.util.function.IntConsumer).

So in either case, forEachRemaining(IntConsumer) will be called, which is the actual implementation method. But when possible, the creation of a boxing adapter will be omitted. The reason is that a Spliterator.OfInt is also a Spliterator<Integer>, which only offers the forEachRemaining(Consumer<Integer>) method. The special behavior allows treating generic Spliterator instances and their primitive (Spliterator.OfPrimitive) counterparts equally, with an automatic selection of the most efficient method.

As said by others, you can implement multiple interfaces with an ordinary class. Also, you can implement multiple interfaces with a lambda expression, if you create a helper type, e.g.

interface UnboxingConsumer extends IntConsumer, Consumer<Integer> {
    public default void accept(Integer t) {
        System.out.println("unboxing "+t);
        accept(t.intValue());
    }
}
public static void printAll(BaseStream<Integer,?> stream) {
    stream.spliterator().forEachRemaining((UnboxingConsumer)System.out::println);
}
public static void main(String[] args) {
    System.out.println("Stream.of(1, 2, 3):");
    printAll(Stream.of(1, 2, 3));
    System.out.println("IntStream.range(0, 3)");
    printAll(IntStream.range(0, 3));
}
Stream.of(1, 2, 3):
unboxing 1
1
unboxing 2
2
unboxing 3
3
IntStream.range(0, 3)
0
1
2
Rotherham answered 31/5, 2017 at 17:4 Comment(3)
good job. explain it more detailed. and it values to up-votes.Flibbertigibbet
@holi-java: what a coincidence, the JRE’s Sink.OfInt you just added to your answer, is a real-life example of the UnboxingConsumer.Rotherham
yes, sir. OfInt.**(Consumer) is a example of UnboxingConsumer, but I say another case. my first answer is solving his ClassCastException problem. after a while, my answer grow another point of view.Flibbertigibbet

© 2022 - 2024 — McMap. All rights reserved.