Ошибка компиляции: Лямбда Тип цели Тип пересечения
public class X {
Object o = (I & J) () -> {};
}
interface I {
public void foo();
}
interface J {
public void foo();
public void bar();
}
Компилятор Oracle выдает ошибку:
X.java:2: error: incompatible types: INT#1 is not a functional interface
Object o = (I & J) () -> {};
^
multiple non-overriding abstract methods found in interface INT#1
where INT#1 is an intersection type:
INT#1 extends Object,I,J
1 error
Компилятор Eclipse компилируется нормально.
Какая реализация выглядит правильно?
Модифицированная форма приведенного выше примера:
public class X {
Object o = (I & J) () -> {};
}
interface I {
public void foo();
}
interface J {
public void foo();
}
Компилятор Eclipse выдает ошибку. Компилятор Oracle принимает это.
Я думаю, что компилятор Oracle правильный.
Рассмотрим контрольный пример:
interface I {
default void foo() { System.out.println("foo I \n"); }
default void bar() { System.out.println("bar I \n"); }
}
interface J extends I {
default void foo() { System.out.println("foo J \n"); }
}
public class Y {
public static void main(String argv[]) throws Exception {
J j = new J() {
};
((I & J) j).foo();
((I & J) j).bar();
}
}
Результат с Oracle и Eclipse Compiler:
foo J
bar I
Исходя из результатов, я могу сделать вывод, что Oracle выглядит правильно.
Дайте мне знать, как вы, ребята, истолковываете это.
Спасибо
3 ответа
В первом примере I&J
не является функциональным интерфейсом (интерфейс с ровно одним абстрактным необъектным методом). Таким образом, Javac является правильным, чтобы выдать ошибку.
Во втором случае I&J
это функциональный интерфейс, поэтому Javac снова правильно.
Похоже, две ошибки в компиляторе Eclipse.
По-видимому, существует путаница (со стороны авторов компилятора Eclipse) в отношении правил, управляющих лямбдами типа пересечений. За
interface I { void foo(); }
interface J { void foo(); }
Затмение жалуется, что
Целевой тип этого выражения не является функциональным интерфейсом: более одного из пересекающихся интерфейсов являются функциональными.
их понимание подразумевает, что тип пересечения не должен рассматриваться как единое целое, но именно один из его компонентов должен быть функциональным интерфейсом.
С другой стороны, правила, управляющие компилятором Oracle, гласят, что результирующий тип пересечения должен сам (в целом) быть функциональным интерфейсом.
Вот соответствующий отчет об ошибке Eclipse, где из комментариев можно сделать вывод об их недоразумении. Ключевая цитата:
- приведение перекрестков для лямбд теперь корректно поддерживается. Мы больше не предполагаем, что первая запись в приведении к пересечению является типом SAM, мы выясняем, какая она есть (если она есть!).
Обратите внимание на слово один. Поэтому они ошибочно полагают, что эти две вещи не могут произойти:
- тип пересечения может содержать типы, которые имеют более одного метода, и это должно быть защищено ошибкой компилятора;
- тип пересечения может содержать несколько типов SAM с одинаковыми методами, сливаясь в допустимый тип SAM.
Их путаница (или отсутствие достаточного внимания), очевидно, вытекает из их предположения о том, что единственный релевантный контекст, где возникают лямбды с пересечением, - это когда один тип SAM объединяется с интерфейсами маркеров, которые имеют нулевые абстрактные методы.
Кстати, в выводе компилятора Oracle для этой строки кода:
I o = (I & J) () -> {};
вот что я нашел:
0: invokedynamic #2, 0 // InvokeDynamic #0:foo:()Ltest/Main$J;
5: checkcast #3 // class test/Main$I
Обратите внимание, что InvokeDynamic
вызов сделан с типом J
, но результат приведен к I
- и это удается. Это кажется довольно тонким.
Первый пример:
Вы переводите лямбда-выражение в тип пересечения. Если результат преобразования должен быть допустимым лямбда-выражением, то это должен быть действительный функциональный интерфейс, как определено в Спецификации языка Java (JLS). На мой взгляд, компилятор Oracle верен, потому что это неверное приведение. Я не знаю точного правила, что оно нарушается в JLS, но я думаю, что оно указано в разделе приведения ссылочного типа (JLS 5.5.1).
Второй пример:
Так как метод foo()
объявлен в обоих интерфейсах, он будет объединен как один метод в любом конкретном классе, который реализует оба. Таким образом, любой тип пересечения будет иметь только один абстрактный метод; что делает его действительным функциональным интерфейсом. Компилятор Oracle демонстрирует правильное поведение.
Примечание:
Если вы хотите, чтобы ваш интерфейс определял сигнатуру метода для лямбда-выражений, обязательно добавьте в него аннотацию @FunctionalInterface
так что инкрементный компилятор Eclipse проверит, чтобы ваш интерфейс считался допустимым функциональным интерфейсом. Это выдаст ошибку для интерфейса J из вашего первого примера, так как он имеет более одного абстрактного метода.