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
, но не...
Что мне не хватает? Это ошибка в компиляторе? Непонимание мое?
Ответы
Ответ 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());
то есть. используя явное выражение лямбда вместо ссылки на метод.