草庐IT

Java8 : ambiguity with lambdas and overloaded methods

coder 2023-05-14 原文

我正在玩 java8 lambdas,但遇到了一个我没想到的编译器错误。

假设我有一个功能 interface A , abstract class B和一个 class C重载方法采用 AB作为参数:

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) .
C c = new C();
c.apply(x -> System.out.println(x));

但是当我改变需要 B 的过载时作为通用版本的参数,编译器报告两个重载不明确。
public class C {
  public void apply(A x) { }    
  public <T extends B> T apply(T x) { return x; }
}

我以为编译器会看到 T必须是 B 的子类这不是一个功能接口(interface)。为什么它不能解决正确的方法?

最佳答案

在重载解析和类型推断的交叉点上有很多复杂性。 current draft lambda 规范的所有细节都有。 F 和 G 部分分别涵盖了重载解析和类型推断。我不假装理解这一切。不过,引言中的摘要部分相当容易理解,我建议人们阅读它们,尤其是 F 和 G 部分的摘要,以了解该领域发生的情况。

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

假设有两个重载方法:

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

并且一些代码具有以下方法调用:
    foo("hello");

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

让我们看看这里的例子。考虑接口(interface) A和抽象类B如示例中所定义。我们有课C包含两个重载,然后一些代码调用 apply方法并将其传递给一个 lambda:
    public void apply(A a)    
    public B apply(B b)

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

两者 apply重载具有相同数量的参数。参数是一个 lambda,它必须匹配一个函数式接口(interface)。 AB是实际类型,所以很明显 A是一个功能接口(interface),而 B不是,因此重载决议的结果是 apply(A) .在这一点上,我们现在有一个目标类型 A对于 lambda 和类型推断 x yield 。

现在的变化:
    public void apply(A a)    
    public <T extends B> T apply(T t)

    c.apply(x -> System.out.println(x));
apply 的第二个重载而不是实际类型是泛型类型变量 T .我们没有做过类型推断,所以我们不取T至少在重载决议完成之后才考虑在内。因此,这两种重载仍然适用,都不是最具体的,并且编译器会发出调用不明确的错误。

你可能会争辩说,因为我们知道 T类型界限为 B ,它是一个类,而不是一个函数接口(interface),lambda 不可能应用于这个重载,因此在重载解析期间应该排除它,消除歧义。我不是那个争论的人。 :-) 这可能确实是编译器中的错误,甚至可能是规范中的错误。

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

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 Expert Group, 9 Aug 2013

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

此决定的后果之一是必须更改某些 API 以避免过载,例如 the Comparator API .以前,Comparator.comparing方法有四个重载:
    comparing(Function)
    comparing(ToDoubleFunction)
    comparing(ToIntFunction)
    comparing(ToLongFunction)

问题是这些重载仅由 lambda 返回类型区分,实际上我们从来没有完全使用类型推断来处理隐式类型的 lambda。为了使用这些,总是必须为 lambda 强制转换或提供显式类型参数。这些 API 后来更改为:
    comparing(Function)
    comparingDouble(ToDoubleFunction)
    comparingInt(ToIntFunction)
    comparingLong(ToLongFunction)

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

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

关于Java8 : ambiguity with lambdas and overloaded methods,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21905169/

有关Java8 : ambiguity with lambdas and overloaded methods的更多相关文章

  1. ruby - 难道Lua没有和Ruby的method_missing相媲美的东西吗? - 2

    我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/

  2. Ruby 元类 : why three when defined singleton methods? - 2

    让我们计算MRI范围内的类别:defcount_classesObjectSpace.count_objects[:T_CLASS]endk=count_classes用类方法定义类:classAdefself.foonilendend然后运行:putscount_classes-k#=>3请解释一下,为什么是三个? 最佳答案 查看MRI代码,每次你创建一个Class时,在Ruby中它是Class类型的对象,ruby会自动为这个新类创建“元类”类,这是另一个单例类型的Class对象。C函数调用(class.c)是:rb_define

  3. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  4. ruby-on-rails - Rails 中的 NoMethodError::MailersController#preview undefined method `activation_token=' for nil:NilClass - 2

    似乎无法为此找到有效的答案。我正在阅读Rails教程的第10章第10.1.2节,但似乎无法使邮件程序预览正常工作。我发现处理错误的所有答案都与教程的不同部分相关,我假设我犯的错误正盯着我的脸。我已经完成并将教程中的代码复制/粘贴到相关文件中,但到目前为止,我还看不出我输入的内容与教程中的内容有什么区别。到目前为止,建议是在函数定义中添加或删除参数user,但这并没有解决问题。触发错误的url是http://localhost:3000/rails/mailers/user_mailer/account_activation.http://localhost:3000/rails/mai

  5. ruby - 怎么来的(a_method || :other) returns :other only when assigning to a var called a_method? - 2

    给定以下方法:defsome_method:valueend以下语句按我的预期工作:some_method||:other#=>:valuex=some_method||:other#=>:value但是下面语句的行为让我感到困惑:some_method=some_method||:other#=>:other它按预期创建了一个名为some_method的局部变量,随后对some_method的调用返回该局部变量的值。但为什么它分配:other而不是:value呢?我知道这可能不是一件明智的事情,并且可以看出它可能有多么模棱两可,但我认为应该在考虑作业之前评估作业的右侧...我已经在R

  6. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  7. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  10. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

随机推荐