0

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?

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Yes, the bridge method explains ClassCastException. Bridge methods are to keep polymorphism and lambda is a kind of anonymous implementation of an interface. But I still can't find cast instruction in a bytecode: private static java.lang.Integer lambda$main$0(java.lang.String); Code: 0: aload_0 1: invokevirtual #62 // Method java/lang/String.length:()I 4: invokestatic #63 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 7: areturn
That's because this bytecode isn't the bridge method's bytecode, it's the actual method's bytecode.
I don't see other related methods in the bytecode using javap -c -p -s -v
I figured out that bridge methods for lambdas aren't inserted during compilation, they are on responsibility of a VM: cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.