Перечисление, интерфейсы и (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());
т. е. использование явного лямбда-выражения вместо метода-ссылки.