25

JDK is Oracle' JDK 1.8u65 but the problem has been seen with "as low as" 1.8u25 as well.

Here is the full SSCCE:

public final class Foo
{
    private interface X
    {
        default void x()
        {
        }
    }

    private enum E1
        implements X
    {
        INSTANCE,
        ;
    }

    private enum E2
        implements X
    {
        INSTANCE,
        ;
    }

    public static void main(final String... args)
    {
        Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(X::x);
    }
}

This code compiles; but it fails at runtime:

Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
    at java.lang.invoke.CallSite.makeSite(CallSite.java:341)
    at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
    at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
    at com.github.fge.grappa.debugger.main.Foo.main(Foo.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class java.lang.Enum; not a subtype of implementation type interface com.github.fge.grappa.debugger.main.Foo$X
    at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233)
    at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
    at java.lang.invoke.CallSite.makeSite(CallSite.java:302)
    ... 8 more

Fixing it in code is "easy"; in the main method, you just have to:

// Note the <X>
Stream.<X>of(E1.INSTANCE, E2.INSTANCE).forEach(X::x);

EDIT There is in fact a second way, as mentioned in the accepted answer... Replace the method reference with a lambda:

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

So, uh. What happens here? Why does the initial code compile in the first place? I'd have expected the compiler to notice that the method reference was not on anything Enum<?> but on X, but no...

What am I missing? Is this a bug in the compiler? A misunderstanding of mine?

9
  • 2
    Perhaps a related javac bug: JDK-8141508 although it seems to deal with intersecting types. Commented Nov 25, 2015 at 20:15
  • 2
    It is an intersecting type. The inferred type of Stream.of(E1.INSTANCE,E2.INSTANCE) is Stream<Enum<?>&Foo.X>, according to my Eclipse. Commented Nov 25, 2015 at 20:22
  • 1
    @Tunaki Easy: you assign it to an unrelated variable, like int i = Stream.of(...), and see what the error message complains about. :-) Commented Nov 25, 2015 at 20:34
  • 1
    @RealSkeptic Nice, I see! :) There I guess this is the same bug as above. Commented Nov 25, 2015 at 20:34
  • 1
    @fge I would think so. The fact that it reports two different types - one when hovering over the of or forEach, which just says it's a stream of Enums, and one when I use the trick above means that the compiler itself is a bit confused. I think one of these allows the X::x, but the other writes byte code that references the wrong type. Commented Nov 25, 2015 at 20:51

1 Answer 1

24

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.

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

4 Comments

Yep, this workaround also works (I have tested it before you answered and stumbled upon this very mail as well, after you linked to the bug). Good digging! Thanks!
I opened an enhancement request on Jetbrains for that: youtrack.jetbrains.com/issue/IDEA-148528. I guess a similar request could be opened for Eclipse :)
@fge Yes I searched their Bugzilla but couldn't find anything.
The older bug that Remi Forax didn’t find is JDK-8058112. Maybe the fact that it is already closed as allegedly fixed hindered finding it. Note that the examples of the related older question still fail with recent JDKs…

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.