I am having a stump of a time understanding the condundrum below. Here is a code snippet that compiles, yet throws the exception
Exception in thread "main" java.lang.ClassCastException:
TestGenericSingleton$$Lambda$1/303563356 cannot be cast to
TestGenericSingleton$IntegerConsumer
at TestGenericSingleton.main(TestGenericSingleton.java:23)
import java.util.function.Consumer;
public class TestGenericSingleton
{
static final Consumer<Object> NOOP_SINGLETON = t -> {System.out.println("NOOP Consumer accepting " + t);};
@SuppressWarnings("unchecked")
static <R extends Consumer<T>, T> R noopConsumer()
{
return (R)NOOP_SINGLETON;
}
static interface IntegerConsumer extends Consumer<Integer> {};
public static void main(String[] argv)
{
Consumer<Boolean> cb = noopConsumer();
cb.accept(true);
IntegerConsumer ic = t -> {System.out.println("NOOP Consumer2 accepting " + t);} ;
ic.accept(3);
ic = noopConsumer();
ic.accept(3);
System.out.println("Done");
}
}
What stumps me is that the Java compiler can generate a proper IntegerConsumer-compatible object out of the lambda on line 20, yet the previously constructed non-generic lambda constructed as the singleton on line 8 can not be used. Is that because the lambda on line 20 has a reifiable subtype of the Consumer that fits the type of the IntegerConsumer reference immediately, whereas the lambda that is cast on line 10 can not be cast at runtime to a real subtype of Consumer ? But then shouldn't the generic bounded type declaration on line 8 take care of that? Any help is really appreciated !
the previously constructed non-generic lambda constructed as the singleton on line 8 can not be used
We will remove lambda and understand the root cause of the exception. Let's consider the below simpler example.
public class TestGenericObject {
static final Object NOOP_SINGLETON = new Object();
static <R extends Object> R noopConsumer() {
return (R) NOOP_SINGLETON;
}
public static void main(String[] argv) {
Object cb = noopConsumer();
Integer ic = noopConsumer(); // Throws java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.Integer
}
}
The exception is justified. NOOP_SINGLETON's actual type is Object but we are trying to cast it to Integer. This would be same as trying, Integer ic = (Integer) new Object()
. In your case, for the same reason you cannot cast Consumer<Object>
type to IntegerConsumer
.
One interesting observation is the exception is not thrown within the noopConsumer()
and is rather thrown in the main()
.
Below is the output of javap -v -c
for the method noopConsumer
... // Removed lines for clarity
static <R extends java.lang.Object> R noopConsumer();
...
Code:
stack=1, locals=0, args_size=0
0: getstatic #2 // Field NOOP_SINGLETON:Ljava/lang/Object;
3: areturn
You can see there is no op code present for casting. But for the main()
public static void main(java.lang.String[]);
Code:
stack=1, locals=3, args_size=1
0: invokestatic #3 // Method noopConsumer:()Ljava/lang/Object;
3: astore_1
4: invokestatic #3 // Method noopConsumer:()Ljava/lang/Object;
7: checkcast #4 // class java/lang/Integer
10: astore_2
11: return
at this line 7:checkcast #4
, it checks if returned type is of Integer. This behaviour is due to 2 reasons
noopConsumer()
, the tighter bound of R is Object and NOOP_SINGLETON is of type Object as well. Hence, post type erasure, the casting is redundant and removed.main()
has a cast check is again due to Type Erasure . As mentioned in the link, if required, type casting will be inserted during type erasure. Back to Lambdas. Lambdas use invokedynamic
opcode to generate code at runtime. This and this are excellent resources to understand better about lambda handling at runtime. For the below code,
public static void main(String[] argv) {
Consumer<Object> NOOP_SINGLETON = t -> {System.out.println("NOOP Consumer accepting " + t);};
TestGenericSingleton.IntegerConsumer ic = t -> {System.out.println("NOOP Consumer2 accepting " + t);} ;
}
lets analyse the byte code.
public static void main(java.lang.String[]);
...
Code:
stack=1, locals=3, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
5: astore_1
6: invokedynamic #3, 0 // InvokeDynamic #1:accept:()Lcom/TestGenericSingleton$IntegerConsumer;
11: astore_2
12: return
The invokedynamic
passes 2 different expected types Ljava/util/function/Consumer
and ()Lcom/TestGenericSingleton$IntegerConsumer
to LambdaMetafactory.metafactory() .
So though the code t -> {System.out.println("NOOP Consumer accepting " + t);}
within the lambdas are same, they are 2 different types.
To summarize , the lambdas are built at runtime and the returned instance will have the type specified in the declaration. Hence, NOOP_SINGLETON is of type Consumer
and ic is of type IntegerConsumer
. Casting from type Consumer
to IntegerConsumer
will fail for the same reason, Integer ic = (Integer)new Object()
fails.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.