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

ern*_*t_k 13 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 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
    }
}
Run Code Online (Sandbox Code Playgroud)

我不明白为什么显式键入 lambda 的参数(A a) -> aList.add(a)会使代码编译。此外,为什么它链接到 inIterable而不是in 的重载CustomIterable
对此是否有一些解释或规范相关部分的链接?

注意:iterable.forEach((A a) -> aList.add(a));仅在CustomIterable<T>扩展时编译Iterable<T>(完全重载方法CustomIterable导致歧义错误)


在两者上都得到这个:

  • openjdk 版本“13.0.2” 2020-01-14
    Eclipse 编译器
  • openjdk 版本“1.8.0_232”
    Eclipse 编译器

编辑:当 Eclipse 成功编译最后一行代码时,上面的代码在使用 maven 构建时无法编译。

Hol*_*ger 8

TL; 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
}
Run Code Online (Sandbox Code Playgroud)

iterable.forEach((A a) -> aList.add(a));语句在 Eclipse 中产生错误。

由于在声明另一个重载时,接口中forEach(Consumer<? super T) c)方法的属性没有Iterable<T>改变,因此 Eclipse 选择此方法的决定不能(始终)基于该方法的任何属性。它仍然是唯一的继承方法,仍然是唯一的default方法,仍然是唯一的JDK方法,等等。无论如何,这些属性都不应该影响方法选择。

请注意,将声明更改为

interface CustomIterable<T> {
    void forEach(ConsumerOne<? super T> c);
    default void forEach(ConsumerTwo<? super T> c) {}
}
Run Code Online (Sandbox Code Playgroud)

也会产生“模棱两可”的错误,因此适用的重载方法的数量也无关紧要,即使只有两个候选方法,也没有对default方法的普遍偏好。

到目前为止,问题似乎出现在有两个适用的方法和一个default方法和一个继承关系时,但这不是进一步挖掘的合适地方。


但是可以理解的是,您的示例的构造可能由编译器中的不同实现代码处理,一个显示错误而另一个没有。
a -> aList.add(a)是一个隐式类型的lambda 表达式,不能用于重载决议。相比之下,(A 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());
}
Run Code Online (Sandbox Code Playgroud)

函数签名不同,使用显式类型的 lambda 表达式确实有助于选择正确的方法,而隐式类型的 lambda 表达式无济于事,因此forEach(s -> s.isEmpty())会产生编译器错误。所有 Java 编译器都同意这一点。

请注意,这aList::add是一个不明确的方法引用,因为该add方法也已重载,因此它也无法帮助选择方法,但无论如何方法引用可能会被不同的代码处理。切换到无歧义aList::contains或更改ListCollection,使之add无歧义,并没有改变我的 Eclipse 安装(我使用2019-06)的结果。