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?
Advertisement
Answer
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.