编译错误:Lambda目标类型交集类型

时间:2014-07-11 08:32:20

标签: java eclipse lambda java-8

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 个答案:

答案 0 :(得分:2)

在第一个示例中,I&J不是功能接口(仅具有一个抽象非Object方法的接口)。所以javac给出错误是正确的。

在第二种情况下,I&J是一个功能界面,所以javac再次正确。

听起来像Eclipse编译器中的两个错误。

答案 1 :(得分:2)

Eclipse编译器的作者显然对管理交集类型lambda的规则感到困惑。对于

interface I { void foo(); } 
interface J { void foo(); }

Eclipse抱怨

  

此表达式的目标类型不是功能接口:多个交叉接口可以正常工作。

暗示他们的理解是不应将交集类型视为一个整体,但其组件类型中的一个必须是一个功能接口。

另一方面,管理Oracle编译器状态的规则表明生成的交集类型本身(作为一个整体)必须是一个功能接口。

Here是一个相关的Eclipse错误报告,从评论中可以推断出他们的误解。关键报价:

  
      
  • 现在可以正确支持lambdas的交集转换。我们不再假设交叉投射中的第一个条目是SAM类型,我们计算出它是哪一个(如果有的话!)。
  •   

注意单词 one 。所以他们错误地认为这两件事情都不会发生:

  • 交集类型可能包含具有多个方法的类型,并且必须防止编译错误;
  • 交集类型可能包含几种具有相同方法的SAM类型,并合并为合法的SAM类型。

他们的混淆(或缺乏足够的注意力)显然源于他们的假设,即交叉型lambda出现的唯一相关背景是当单个SAM类型与标记接口组合时,它们为零抽象方法。

BTW在这段代码中查看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 - 并且成功。这看起来非常微妙。

答案 2 :(得分:0)

第一个例子:

您正在将lambda表达式转换为交集类型。如果强制转换的结果是有效的lambda表达式,那么它必须是有效的functional interface as defined in the Java Language Specification (JLS)。在我看来,Oracle编译器是正确的,因为这是一个无效的强制转换。我不知道它在JLS中打破的确切规则,但我想它已在reference type casting section (JLS 5.5.1)中列出。

第二个例子:

由于方法foo()在两个接口中都被声明,因此它将作为实现两者的任何具体类中的一个方法合并。因此任何交集类型只有一个抽象方法;这使它成为一个有效的功能界面。 Oracle编译器表现出正确的行为。

旁注:

如果您希望接口为lambda表达式定义方法签名,请确保使用@FunctionalInterface对其进行批注,以便增量Eclipse编译器检查以确保您的接口被视为有效的功能接口。这将从第一个示例中为接口J引发错误,因为它有多个抽象方法。