Lambdaconversionexception with Generics: Jvm Bug

LambdaConversionException with generics: JVM bug?

Here is a simplified example which reproduces the problem and uses only core Java classes:

public static void main(String[] argv) {
System.out.println(dummy("foo"));
}
static <T extends Serializable&CharSequence> int dummy(T value) {
return Optional.ofNullable(value).map(CharSequence::length).orElse(0);
}

Your assumption is correct, the JRE-specific implementation receives the target method as a MethodHandle which has no information about generic types. Therefore the only thing it sees is that the raw types mismatch.

Like with a lot of generic constructs, there is a type cast required on the byte code level which doesn’t appear in the source code. Since LambdaMetafactory explicitly requires a direct method handle, a method reference which encapsulates such a type cast cannot be passed as a MethodHandle to the factory.

There are two possible ways to deal with it.

First solution would be to change the LambdaMetafactory to trust the MethodHandle if the receiver type is an interface and insert the required type cast by itself in the generated lambda class instead of rejecting it. After all, it does similar for parameter and return types already.

Alternatively, the compiler would be in charge to create a synthetic helper method encapsulating the type cast and method call, just like if you had written a lambda expression. This is not a unique situation. If you use a method reference to a varargs method or an array creation like, e.g. String[]::new, they can’t be expressed as direct method handles and end up in synthetic helper methods.

In either case, we can consider the current behavior a bug. But obviously, compiler and JRE developers must agree on which way it should be handled before we can say on which side the bug resides.

LambdaConversionException when mixing method reference and generics

It seems to be a Netbeans issue and I can't reproduce the problem when using javac from the command line. I have filed a bug report.

generic types paired with method references

After preparing this question for 20 minutes, I did a google search and found the issue immediately :| this one

The fix is easy, just use a lambda expression:

(T t) -> t.getValue() // instead of T::getValue

Enum, interfaces and (Java 8) lambdas: code compiles but fails at runtime; is this expected?

It seems you've hit JDK-8141508, which is indeed a bug of javac when dealing with intersection types and method-references. It is scheduled to be fixed in Java 9.

Quoting a mail from Remi Forax:

javac has trouble with intersection type that are target type of a lambda and method reference,
Usually when there is an intersection type, javac substitute it by the first type of the intersection type and add cast when necessary.

Let suppose we have this code,

public class Intersection {
interface I {
}
interface J {
void foo();
}

static <T extends I & J> void bar(T t) {
Runnable r = t::foo;
}

public static void main(String[] args) {
class A implements I, J { public void foo() {} }
bar(new A());
}
}

Currently, javac generates a method reference on J::foo with an invokedynamic that takes an I as parameter, hence it fails at runtime.
javac should de-sugar t::foo into a lambda that take an I and then add a cast to J like for a call to a method of an intersection type.

So the workaround is to use a lambda instead,

Runnable r = t -> t.foo();

I've already seen this bug somewhere but was not able to find a corresponding bug report in the database :(

In your code, the Stream created by Stream.of(E1.INSTANCE, E2.INSTANCE) is of type Stream<Enum<?>&Foo.X>, which combines all the elements of the bug: intersecting types and method-references.

As noted by Remi Forax, a work-around would be:

Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());

i.e. using an explicit lambda expression instead of a method-reference.

Weird exception Invalid receiver type class java.lang.Object; not a subtype of ...

You ran into the same compiler bug that has been discussed in this question and that question.

The problem occurs whenever an intersection type is involved and you are using a method reference using a receiver type other than the first one (the first type is the one that will remain after type erasure).

So when you replace the method reference with a lambda expression, you are not affected by the bug anymore. If you remove the Serializable from the types instead, the inferred element type of the Stream will be Fruit, i.e. not an intersection type, and again the problem does not occur. But with the two element types implementing Fruit and Serializable, the compiler will infer the element type Object&Fruit&Serializable and the raw type will be Object which provokes the error when using a method reference with the receiver type Fruit. You can easily work around this:

Stream.of(apples.stream(), oranges.stream())
.<Fruit>flatMap(Function.identity())
.map(Fruit::getPickingMonth) // no more exception on this line
.forEachOrdered(System.out::println);

The compiled code will be identical to your original, but the formal result type of the flatMap operation will be Stream<Fruit>, ignoring all other artifacts of the inferred intersection type. As a consequence the method reference Fruit::getPickingMonth will implement the type Function<Fruit,Integer> instead of Function<Object&Fruit&Serializable,Integer> and the compiler bug does not materialize.

But note that your code is unnecessarily complicated. You can simply use

Stream.<Fruit>concat(apples.stream(), oranges.stream())
.map(Fruit::getPickingMonth) // no more exception on this line
.forEachOrdered(System.out::println);

to achieve the same.



Related Topics



Leave a reply



Submit