Подтвердить что ты не робот

Enum, интерфейсы и (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);

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

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

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

Что мне не хватает? Это ошибка в компиляторе? Непонимание мое?

4b9b3361

Ответ 1

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

Цитата почта от Remi Forax:

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

Предположим, что мы имеем этот код,

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, которая принимает параметр я как параметр, поэтому он не работает во время выполнения. javac должен де сахара t:: foo в лямбда, который принимает I, а затем добавляет приведение в J, как для вызова метода типа пересечения.

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

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

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

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

Как отметил Реми Форакс, обход будет:

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

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