Java 具有多个匹配目标类型的lambda表达式的方法签名选择

Java 具有多个匹配目标类型的lambda表达式的方法签名选择,java,generics,lambda,javac,ecj,Java,Generics,Lambda,Javac,Ecj,我在回答时遇到了一个无法解释的情况。考虑这个代码: interface ConsumerOne<T> { void accept(T a); } interface CustomIterable<T> extends Iterable<T> { void forEach(ConsumerOne<? super T> c); //overload } class A { private static CustomItera

我在回答时遇到了一个无法解释的情况。考虑这个代码:

interface ConsumerOne<T> {
    void accept(T a);
}

interface CustomIterable<T> extends Iterable<T> {
    void forEach(ConsumerOne<? super T> c); //overload
}

class A {
    private static CustomIterable<A> iterable;
    private static List<A> aList;

    public static void main(String[] args) {
        iterable.forEach(a -> aList.add(a));     //ambiguous
        iterable.forEach(aList::add);            //ambiguous

        iterable.forEach((A a) -> aList.add(a)); //OK
    }
}
interface-ConsumerOne{
无效接受(T a);
}
接口可定制扩展可定制{

void forEach(ConsumerOneTL;DR,这是一个编译器错误

当某个特定的可应用方法被继承或是默认方法时,没有规则会给予它优先权

interface ConsumerOne<T> {
    void accept(T a);
}
interface ConsumerTwo<T> {
  void accept(T a);
}

interface CustomIterable<T> extends Iterable<T> {
    void forEach(ConsumerOne<? super T> c); //overload
    void forEach(ConsumerTwo<? super T> c); //another overload
}
也会产生一个“不明确”错误,因此适用的重载方法的数量也无关紧要,即使只有两个候选方法,也没有对
default
方法的一般偏好

到目前为止,当有两个适用的方法和一个
default
方法以及一个继承关系涉及时,问题似乎出现了,但这不是进一步挖掘的正确地方


但是可以理解的是,您的示例的构造可能由编译器中的不同实现代码处理,一个显示错误,而另一个没有。
a->aList.add(a)
是一个隐式类型的lambda表达式,不能用于重载解析。相反,
(a)->aList.add(a)
是一个显式类型的lambda表达式,可用于从重载方法中选择匹配方法,但在这里没有帮助(这里不应该有帮助),因为所有方法都具有完全相同的函数签名的参数类型

作为反例,使用

static void forEach(Consumer<String> c) {}
static void forEach(Predicate<String> c) {}
{
  forEach(s -> s.isEmpty());
  forEach((String s) -> s.isEmpty());
}
static void forEach(消费者c){
静态void forEach(谓词c){}
{
forEach(s->s.isEmpty());
forEach((字符串s)->s.isEmpty());
}
函数签名不同,使用显式类型的lambda表达式确实有助于选择正确的方法,而隐式类型的lambda表达式没有帮助,因此
forEach(s->s.isEmpty())
会产生编译器错误。所有Java编译器都同意这一点


请注意,
aList::add
是一个不明确的方法引用,因为
add
方法也是重载的,所以它也不能帮助选择一个方法,但方法引用可能会被不同的代码处理。切换到明确的
aList::contains
或将
列表
更改为
集合
,to使
add
明确无误,并没有改变我的Eclipse安装结果(我使用了
2019-06
)。

Eclipse编译器正确解析为
默认方法
方法
,因为这是最具体的方法

如果最具体的方法中只有一种是具体的(即, 非
抽象或默认),它是最具体的方法

javac
(默认情况下由Maven和IntelliJ使用)告诉我们这里的方法调用是不明确的。但是根据Java语言规范,它不是不明确的,因为这两个方法中的一个是这里最具体的方法

隐式类型的lambda表达式的处理方式与Java中显式类型的lambda表达式的处理方式不同。隐式类型的lambda表达式与显式类型的lambda表达式相比,需要经过第一个阶段来确定严格调用的方法(请参阅第一点)因此,对于隐式类型的lambda表达式,这里的方法调用是不明确的

在您的例子中,这个
javac
错误的解决方法是指定函数接口的类型,而不是使用显式类型的lambda表达式,如下所示:

iterable.forEach((ConsumerOne<A>) aList::add);
iterable.forEach((ConsumerOne)aList::add);

iterable.forEach((Consumer)aList::add);
以下是进一步简化测试的示例:

class A {

    interface FunctionA { void f(A a); }
    interface FunctionB { void f(A a); }

    interface FooA {
        default void foo(FunctionA functionA) {}
    }

    interface FooAB extends FooA {
        void foo(FunctionB functionB);
    }

    public static void main(String[] args) {
        FooAB foo = new FooAB() {
            @Override public void foo(FunctionA functionA) {
                System.out.println("FooA::foo");
            }
            @Override public void foo(FunctionB functionB) {
                System.out.println("FooAB::foo");
            }
        };
        java.util.List<A> list = new java.util.ArrayList<A>();

        foo.foo(a -> list.add(a));      // ambiguous
        foo.foo(list::add);             // ambiguous

        foo.foo((A a) -> list.add(a));  // not ambiguous (since FooA::foo is default; javac bug)
    }

}
A类{
界面函数{void f(A);}
接口函数b{void f(A);}
接口FooA{
默认的void foo(function){}
}
接口FooAB扩展了FooA{
void foo(函数b函数b);
}
公共静态void main(字符串[]args){
FooAB foo=新的FooAB(){
@覆盖公共无效foo(函数函数){
System.out.println(“FooA::foo”);
}
@覆盖公共void foo(函数b函数b){
System.out.println(“FooAB::foo”);
}
};
java.util.List List=new java.util.ArrayList();
foo.foo(a->list.add(a));//不明确
foo.foo(list::add);//不明确
foo.foo((A)->list.add(A));//不含糊(因为FooA::foo是默认值;javac bug)
}
}

Eclipse实现JLS§15.12.2.5的代码没有发现任何一种方法比另一种更具体,即使是显式类型的lambda

因此,理想情况下Eclipse会停在这里并报告歧义。不幸的是,除了实现JLS之外,重载解析的实现还有很多代码。根据我的理解,必须保留这些代码(可以追溯到Java 5是新版本时),以填补JLS中的一些空白

我已经立案追踪这件事了


与此特定的错误无关,我只能强烈建议不要使用这种类型的代码。在Java中,重载有助于带来大量惊喜,再加上lambda类型推断,复杂性与感知的收益不成比例。

这三个错误都不是在Java 8上编译的。现在我不确定这是否是一个在Java中修复的错误更新的版本,或引入的错误/功能…您可能应该指定Javaversion@Sweeper我最初是使用jdk-13获得的。随后在java 8(jdk8u232)中进行的测试显示了相同的错误。不确定为什么最后一个没有在您的计算机上编译。也无法在两个联机编译器上复制(,).我在我的机器上使用的是1.8.0221。这越来越奇怪,越来越复杂
iterable.forEach((Consumer<A>) aList::add);
class A {

    interface FunctionA { void f(A a); }
    interface FunctionB { void f(A a); }

    interface FooA {
        default void foo(FunctionA functionA) {}
    }

    interface FooAB extends FooA {
        void foo(FunctionB functionB);
    }

    public static void main(String[] args) {
        FooAB foo = new FooAB() {
            @Override public void foo(FunctionA functionA) {
                System.out.println("FooA::foo");
            }
            @Override public void foo(FunctionB functionB) {
                System.out.println("FooAB::foo");
            }
        };
        java.util.List<A> list = new java.util.ArrayList<A>();

        foo.foo(a -> list.add(a));      // ambiguous
        foo.foo(list::add);             // ambiguous

        foo.foo((A a) -> list.add(a));  // not ambiguous (since FooA::foo is default; javac bug)
    }

}