具有多个匹配目标类型的 lambda 表达式的方法签名选择 [英] Method signature selection for lambda expression with multiple matching target types

查看:28
本文介绍了具有多个匹配目标类型的 lambda 表达式的方法签名选择的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在回答 一个问题并遇到了一个我无法解释的场景.考虑这个代码:

interface ConsumerOne;{无效接受(T a);}接口 CustomIterable扩展 Iterable{void forEach(ConsumerOne c);//超载}A类{私有静态 CustomIterable可迭代的;私有静态列表一个列表;公共静态无效主(字符串 [] args){iterable.forEach(a -> aList.add(a));//模糊的iterable.forEach(aList::add);//模糊的iterable.forEach((A a) -> aList.add(a));//好的}}

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

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

<小时>

在两者上都得到这个:

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

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

解决方案

TL;DR,这是一个编译器错误.

没有规则会在继承或默认方法时优先考虑特定的适用方法.有趣的是,当我将代码更改为

interface ConsumerOne;{无效接受(T a);}接口 ConsumerTwo<T>{无效接受(T a);}接口 CustomIterable扩展 Iterable{void forEach(ConsumerOne c);//超载void forEach(ConsumerTwo c);//另一个重载}

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

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

注意将声明改为

interface CustomIterable;{void forEach(ConsumerOne c);默认 void forEach(ConsumerTwo c) {}}

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

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

<小时>

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

举个反例

static void forEach(Consumer c) {}static void forEach(Predicate c) {}{forEach(s -> s.isEmpty());forEach((String s) -> s.isEmpty());}

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

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

I was answering a question and ran into a scenario I can't explain. Consider this code:

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
    }
}

I do not understand why explicitly typing the paramter of the lambda (A a) -> aList.add(a) makes the code compile. Additionally, why does it link to the overload in Iterable rather than the one in CustomIterable?
Is there some explanation to this or a link to the relevant section of the spec?

Note: iterable.forEach((A a) -> aList.add(a)); only compiles when CustomIterable<T> extends Iterable<T> (flatly overloading the methods in CustomIterable results in the ambiguous error)


Getting this on both:

  • openjdk version "13.0.2" 2020-01-14
    Eclipse compiler
  • openjdk version "1.8.0_232"
    Eclipse compiler

Edit: The code above fails to compile on building with maven while Eclipse compiles the last line of code successfully.

解决方案

TL;DR, this is a compiler bug.

There is no rule that would give precedence to a particular applicable method when it is inherited or a default method. Interestingly, when I change the code to

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
}

the iterable.forEach((A a) -> aList.add(a)); statement produces an error in Eclipse.

Since no property of the forEach(Consumer<? super T) c) method from the Iterable<T> interface changed when declaring another overload, Eclipse’s decision to select this method can not be (consistently) based on any property of the method. It’s still the only inherited method, still the only default method, still the only JDK method, and so on. Neither of these properties should affect the method selection anyway.

Note that changing the declaration to

interface CustomIterable<T> {
    void forEach(ConsumerOne<? super T> c);
    default void forEach(ConsumerTwo<? super T> c) {}
}

also produces an "ambiguous" error, so the number of applicable overloaded methods doesn’t matter either, even when there are only two candidates, there is no general preference towards default methods.

So far, the issue seems to appear when there are two applicable methods and a default method and an inheritance relationship are involved, but this is not the right place to dig further.


But it’s understandable that the constructs of your example may be handled by different implementation code in the compiler, one exhibiting a bug while the other doesn’t.
a -> aList.add(a) is an implicitly typed lambda expression, which can’t be used for the overload resolution. In contrast, (A a) -> aList.add(a) is an explicitly typed lambda expression which can be used to select a matching method from the overloaded methods, but it doesn’t help here (shouldn’t help here), as all methods have parameter types with exactly the same functional signature.

As a counter-example, with

static void forEach(Consumer<String> c) {}
static void forEach(Predicate<String> c) {}
{
  forEach(s -> s.isEmpty());
  forEach((String s) -> s.isEmpty());
}

the functional signatures differ, and using an explicitly type lambda expression can indeed help selecting the right method whereas the implicitly typed lambda expression doesn’t help, so forEach(s -> s.isEmpty()) produces a compiler error. And all Java compilers agree about that.

Note that aList::add is an ambiguous method reference, as the add method is overloaded too, so it also can’t help selecting a method, but method references might get processed by different code anyway. Switching to an unambiguous aList::contains or changing List to Collection, to make add unambiguous, did not change the outcome in my Eclipse installation (I used 2019-06).

这篇关于具有多个匹配目标类型的 lambda 表达式的方法签名选择的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆