Перечисление, интерфейсы и (Java 8) lambdas: код компилируется, но не выполняется во время выполнения; это ожидается?

JDK-это Oracle ' JDK 1.8u65 но проблема была замечена с "как низко как" 1.8u25 также.

вот полный 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);
    }
}

этот код компилируется, но он терпит неудачу во время выполнения:

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

исправление его в коде "легко"; в основном методе вам просто нужно:

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

редактировать на самом деле есть второй способ, как упоминалось в принятом ответе... Заменить эталонный метод с лямбда:

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

так, ну. Что здесь происходит? Почему исходный код компилируется в первую очередь? Я ожидал, что компилятор заметит, что ссылка на метод не была ни на что Enum<?> но на X, но нет...

что я упустил? Это ошибка в компиляторе? Мое недопонимание?

1 ответов


кажется, вы попали JDK-8141508, что действительно ошибка javac при работе с типами пересечений и ссылками на методы. Это планируется исправить в Java 9.

цитирую письмо от Реми Forax:

javac имеет проблемы с типом пересечения, которые являются целевым типом ссылки лямбда и метода, Обычно, когда есть тип пересечения, javac заменяет его первым типом типа пересечения и при необходимости добавьте cast.

предположим, у нас есть этот код,

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());
      }
  }

в настоящее время javac генерирует ссылку на метод J::foo с invokedynamic, который принимает I как параметр, следовательно, он терпит неудачу во время выполнения. javac должен де-сахар t:: foo в лямбду, которая принимает I, а затем добавляет приведение к J, как для вызова метода типа пересечения.

таким образом, обходным путем является использование лямбды вместо этого,

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

Я уже видел эта ошибка где-то, но не удалось найти соответствующий отчет об ошибке в базе данных: (

в вашем коде поток, созданный Stream.of(E1.INSTANCE, E2.INSTANCE) типа Stream<Enum<?>&Foo.X>, который объединяет все элементы ошибки: пересекающиеся типы и метод-ссылки.

как отметил Реми Форакс, обходной путь будет:

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

т. е. использование явного лямбда-выражения вместо метода-ссылки.