Java8:lambdas 和重载方法的歧义

编程入门 行业动态 更新时间:2024-10-25 16:17:17
本文介绍了Java8:lambdas 和重载方法的歧义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我在玩 java8 lambdas 时遇到了一个我没想到的编译器错误.

I'm playing around with java8 lambdas and I came across a compiler error which I didn't expect.

假设我有一个函数式接口 A、一个 抽象类 B 和一个 class C 带有重载方法,它们采用 A 或 B 作为参数:

Say I have a functional interface A, an abstract class B and a class C with overloaded methods that take either A or B as arguments:

public interface A { void invoke(String arg); } public abstract class B { public abstract void invoke(String arg); } public class C { public void apply(A x) { } public B apply(B x) { return x; } }

然后我可以将一个 lambda 传递给 c.apply 并正确解析为 c.apply(A).

Then I can pass a lambda into c.apply and it is correctly resolved to c.apply(A).

C c = new C(); c.apply(x -> System.out.println(x));

但是当我将将 B 作为参数的重载更改为通用版本时,编译器报告说这两个重载不明确.

But when I change the overload that takes B as argument to a generic version the compiler reports that the two overloads are ambiguous.

public class C { public void apply(A x) { } public <T extends B> T apply(T x) { return x; } }

我认为编译器会看到 T 必须是 B 的子类,它不是一个功能接口.为什么不能解析正确的方法?

I thought the compiler would see that T has to be a subclass of B which is not a functional interface. Why can't it resolve the correct method?

推荐答案

在重载解析和类型推断的交集处有很多复杂性.lambda 规范的当前草案包含所有血腥细节.F 和 G 部分分别涵盖了重载解析和类型推断.我不假装理解这一切.不过,引言中的摘要部分相当容易理解,我建议人们阅读它们,尤其是 F 和 G 部分的摘要,以了解该领域的情况.

There is a lot of complexity at the intersection of overload resolution and type inference. The current draft of the lambda specification has all the gory details. Sections F and G cover overload resolution and type inference, respectively. I don't pretend to understand it all. The summary sections in the introduction are fairly understandable, though, and I recommend that people read them, particularly the summaries of sections F and G, to get an idea of what's going on in this area.

为了简要回顾这些问题,请考虑在存在重载方法的情况下使用一些参数的方法调用.重载解析必须选择正确的方法来调用.方法的形状"(数量或参数数量)是最重要的;显然,带有一个参数的方法调用不能解析为带有两个参数的方法.但是重载方法通常具有相同数量的不同类型的参数.在这种情况下,类型开始变得重要.

To recap the issues briefly, consider a method call with some arguments in the presence of overloaded methods. Overload resolution has to choose the right method to call. The "shape" of the method (arity, or number of arguments) is most significant; obviously a method call with one argument can't resolve to a method that takes two parameters. But overloaded methods often have the same number of parameters of different types. In this case, the types start to matter.

假设有两个重载方法:

void foo(int i); void foo(String s);

并且某些代码具有以下方法调用:

and some code has the following method call:

foo("hello");

显然,根据传递的参数类型,这可以解析为第二种方法.但是如果我们正在做重载解析,并且参数是一个 lambda 呢?(特别是那些类型是隐式的,它依赖于类型推断来建立类型.)回想一下,lambda 表达式的类型是从目标类型推断出来的,即在此上下文中预期的类型.不幸的是,如果我们有重载方法,那么在我们确定要调用哪个重载方法之前,我们没有目标类型.但是因为我们还没有 lambda 表达式的类型,所以我们不能在重载解析期间使用它的类型来帮助我们.

Obviously this resolves to the second method, based on the type of the argument being passed. But what if we are doing overload resolution, and the argument is a lambda? (Especially one whose types are implicit, that relies on type inference to establish the types.) Recall that a lambda expression's type is inferred from the target type, that is, the type expected in this context. Unfortunately, if we have overloaded methods, we don't have a target type until we've resolved which overloaded method we're going to call. But since we don't yet have a type for the lambda expression, we can't use its type to help us during overload resolution.

让我们看看这里的例子.考虑示例中定义的接口 A 和抽象类 B.我们有包含两个重载的 C 类,然后一些代码调用 apply 方法并传递给它一个 lambda:

Let's look at the example here. Consider interface A and abstract class B as defined in the example. We have class C that contains two overloads, and then some code calls the apply method and passes it a lambda:

public void apply(A a) public B apply(B b) c.apply(x -> System.out.println(x));

两个 apply 重载具有相同数量的参数.参数是一个 lambda,它必须匹配一个函数接口.A 和 B 是实际类型,所以很明显 A 是一个函数接口,而 B 不是,因此重载解析的结果是apply(A).在这一点上,我们现在有了 lambda 的目标类型 A,然后对 x 进行类型推断.

Both apply overloads have the same number of parameters. The argument is a lambda, which must match a functional interface. A and B are actual types, so it's manifest that A is a functional interface whereas B is not, therefore the result of overload resolution is apply(A). At this point we now have a target type A for the lambda, and type inference for x proceeds.

现在的变化:

public void apply(A a) public <T extends B> T apply(T t) c.apply(x -> System.out.println(x));

apply 的第二个重载不是实际类型,而是泛型类型变量 T.我们没有做类型推断,所以我们不考虑T,至少在重载解析完成之后才考虑.因此这两种重载仍然适用,都不是最具体的,并且编译器会发出调用不明确的错误.

Instead of an actual type, the second overload of apply is a generic type variable T. We haven't done type inference, so we don't take T into account, at least not until after overload resolution has completed. Thus both overloads are still applicable, neither is most specific, and the compiler emits an error that the call is ambiguous.

你可能会争辩说,因为我们知道 T 有一个 B 的类型绑定,它是一个类,而不是一个功能接口, lambda 不可能应用于此重载,因此应在重载解析期间排除它,以消除歧义.我不是那个争论的人.:-) 这可能确实是编译器或规范中的错误.

You might argue that, since we know that T has a type bound of B, which is a class, not a functional interface, the lambda can't possibly apply to this overload, thus it should be ruled out during overload resolution, removing the ambiguity. I'm not the one to have that argument with. :-) This might indeed be a bug in either the compiler or perhaps even in the specification.

我确实知道在 Java 8 的设计过程中这个领域经历了一系列的变化.早期的变化确实试图将更多的类型检查和推理信息带入重载解析阶段,但它们更难实现、指定和理解.(是的,比现在更难理解.)不幸的是,问题不断出现.决定通过减少可以重载的事物的范围来简化事物.

I do know that this area went through a bunch of changes during the design of Java 8. Earlier variations did attempt to bring more type checking and inference information into the overload resolution phase, but they were harder to implement, specify, and understand. (Yes, even harder to understand than it is now.) Unfortunately problems kept arising. It was decided to simplify things by reducing the range of things that can be overloaded.

类型推断和重载从来都是对立的;许多从第 1 天开始进行类型推断的语言就禁止重载(除了可能在 arity 上的情况.)因此,对于需要推断的隐式 lambda 等结构,放弃重载能力以增加可以使用隐式 lambda 的情况似乎是合理的.

Type inference and overloading are ever in opposition; many languages with type inference from day 1 prohibit overloading (except maybe on arity.) So for constructs like implicit lambdas, which require inference, it seems reasonable to give up something in overloading power to increase the range of cases where implicit lambdas can be used.

-- Brian Goetz,Lambda 专家组,2013 年 8 月 9 日

(这是一个颇有争议的决定.请注意,该线程中有 116 条消息,还有其他几个线程讨论了这个问题.)

(This was quite a controversial decision. Note that there were 116 messages in this thread, and there are several other threads that discuss this issue.)

该决定的后果之一是必须更改某些 API 以避免过载,例如 比较器 API.以前,Comparatorparing 方法有四个重载:

One of the consequences of this decision was that certain APIs had to be changed to avoid overloading, for example, the Comparator API. Previously, the Comparatorparing method had four overloads:

comparing(Function) comparing(ToDoubleFunction) comparing(ToIntFunction) comparing(ToLongFunction)

问题是这些重载仅通过 lambda 返回类型来区分,实际上我们从来没有完全使用类型推断来处理隐式类型的 lambda.为了使用这些,总是必须为 lambda 强制转换或提供显式类型参数.这些 API 后来更改为:

The problem was that these overloads are differentiated only by the lambda return type, and we actually never quite got the type inference to work here with implicitly-typed lambdas. In order to use these one would always have to cast or supply an explicit type argument for the lambda. These APIs were later changed to:

comparing(Function) comparingDouble(ToDoubleFunction) comparingInt(ToIntFunction) comparingLong(ToLongFunction)

这有点笨拙,但完全没有歧义.类似的情况发生在 Stream.map、mapToDouble、mapToInt 和 mapToLong 以及其他一些地方围绕 API.

which is somewhat clumsy, but it's entirely unambiguous. A similar situation occurs with Stream.map, mapToDouble, mapToInt, and mapToLong, and in a few other places around the API.

最重要的是,在存在类型推断的情况下获得正确的重载解析通常非常困难,并且语言和编译器设计者为了使类型推断更好地工作而牺牲了重载解析的能力.出于这个原因,Java 8 API 避免了预期使用隐式类型 lambda 的重载方法.

The bottom line is that getting overload resolution right in the presence of type inference is very difficult in general, and that the language and compiler designers traded away power from overload resolution in order to make type inference work better. For this reason, the Java 8 APIs avoid overloaded methods where implicitly typed lambdas are expected to be used.

更多推荐

Java8:lambdas 和重载方法的歧义

本文发布于:2023-11-07 06:45:44,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1565799.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:歧义   方法   lambdas

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!