Consider this example:
Function<String, Integer> function = String::length;
Function rawFunction = function; // warning: unchecked conversion
rawFunction.apply(new Object()); // warning: unchecked call
The last line will give java.lang.ClassCastException: class java.lang. Object cannot be cast to class java.lang.String
. This is not a surprise for me, as Function is declared to accept String.
I know that raw types can give ClassCastException, but all examples I saw are about ClassCastException on unchecked call return object not about method arguments: I can find cast added by a compiler in a bytecode for return object but not for arguments. Where this behavior is specified? What exactly is causing ClassCastException from my example if not an instruction in a bytecode?
CodePudding user response:
The way generics work with method parameters is via synthetic bridge methods.
For example, for the Function
interface - whose raw parameter accepts an Object
, but the non-raw parameter accepts a whatever, you have to have a method that accepts the Object
.
When I decompiled the code above, I got a line in the bytecode:
16: invokeinterface #4, 2 // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
so it's actually trying to invoke a method apply
, which takes an Object
as the parameter. This method exists on the Function
, this is the synthetic bridge method.
If you write a Function
like this:
class MyFunction implements Function<String, Integer> {
@Override public Integer apply(String input) {
return null;
}
}
and then decompile it, you will find there is another method there:
final class MyFunction implements java.util.function.Function<java.lang.String, java.lang.Integer> {
// Default constructor, as you'd expect.
MyFunction();
// The apply method as defined above.
public java.lang.Integer apply(java.lang.String);
// What's this?!
public java.lang.Object apply(java.lang.Object);
}
The public java.lang.Object apply(java.lang.Object)
method has been added. This is the synthetic bridge method.
Its bytecode looks like:
public java.lang.Object apply(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #2 // class java/lang/String
5: invokevirtual #3 // Method apply:(Ljava/lang/String;)Ljava/lang/Integer;
8: areturn
which is something like:
public Object apply(Object input) {
return apply((String) input);
}
Hence, the synthetic bridge method just calls the "non-raw" apply method. So, the ClassCastException
comes from that cast the synthetic bridge method.