Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/363.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java8:lambdas和重载方法的歧义_Java_Generics_Lambda_Java 8 - Fatal编程技术网

Java8:lambdas和重载方法的歧义

Java8:lambdas和重载方法的歧义,java,generics,lambda,java-8,Java,Generics,Lambda,Java 8,我在玩Java8Lambdas时遇到了一个我没有预料到的编译器错误 假设我有一个功能性的接口a、一个抽象类B和一个类C,其中重载方法将a或B作为参数: 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) { }

我在玩Java8Lambdas时遇到了一个我没有预料到的编译器错误

假设我有一个功能性的
接口a
、一个
抽象类B
和一个
类C
,其中重载方法将
a
B
作为参数:

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)

但是,当我将以
B
为参数的重载更改为泛型版本时,编译器会报告这两个重载是不明确的

public class C {
  public void apply(A x) { }    
  public <T extends B> T apply(T x) { return x; }
}
公共C类{
公共无效应用(A x){}
公共T应用(tx){return x;}
}

我以为编译器会看到
T
必须是
B
的子类,而不是函数接口。为什么它不能解决正确的方法

我相信答案是B的子类型T可能实现a,因此对于这样的类型T的参数,分配给哪个函数是不明确的。

在重载解析和类型推断的交叉点上有很多复杂性。lambda规范的定义包含了所有血淋淋的细节。第F和G节分别涉及重载解析和类型推断。我并不假装完全明白。不过,导言中的摘要部分是可以理解的,我建议大家阅读它们,特别是F和G部分的摘要,以了解这方面的情况

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

假设有两个重载方法:

    void foo(int i);
    void foo(String s);
某些代码具有以下方法调用:

    foo("hello");
显然,这会根据传递的参数类型解析为第二种方法。但是如果我们做的是重载解析,而这个参数是lambda呢?(特别是那些类型是隐式的,依赖类型推断来建立类型的)回想一下,lambda表达式的类型是从目标类型推断出来的,也就是说,在这个上下文中预期的类型。不幸的是,如果我们有重载方法,那么在我们解决了要调用哪个重载方法之前,我们没有目标类型。但是,因为我们还没有lambda表达式的类型,所以在重载解析期间,我们不能使用它的类型来帮助我们

让我们看看这里的例子。请考虑接口<代码> > <代码>和在示例中定义的抽象类<代码> b>代码>。我们有一个包含两个重载的类
C
,然后一些代码调用
apply
方法并向其传递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
的类型推断继续进行

现在是变化:

    public void apply(A a)    
    public <T extends B> T apply(T t)

    c.apply(x -> System.out.println(x));
问题是,这些重载只通过lambda返回类型来区分,实际上,我们在这里从未得到过隐式类型lambda的类型推断。为了使用这些参数,必须始终为lambda强制转换或提供显式类型参数。这些API后来更改为:

    comparing(Function)
    comparingDouble(ToDoubleFunction)
    comparingInt(ToIntFunction)
    comparingLong(ToLongFunction)
这有点笨拙,但它是完全明确的。类似的情况也发生在
Stream.map
mapToDouble
mapToInt
mapToLong
以及API周围的一些其他地方


归根结底,在存在类型推理的情况下获得正确的重载解析通常是非常困难的,语言和编译器设计者为了使类型推理更好地工作,牺牲了重载解析的能力。因此,Java 8 API避免了使用隐式类型lambda的重载方法。

我认为这个测试用例暴露了一种情况,在这种情况下,Java 8编译器可以做更多的工作来尝试放弃不适用的重载候选方法,第二种方法是:

public class C {
    public void apply(A x) { }    
    public <T extends B> T apply(T x) { return x; }
}
公共C类{
公共无效应用(A x){}
公共T应用(tx){return x;}
}
基于T永远不能被实例化为功能接口这一事实。这个案子很有趣@谢谢你问这个问题。我会调查这项建议的利弊

现在反对实现这一点的主要论点可能是这段代码的频率有多高。我猜想,一旦人们开始将当前的Java代码转换为Java8,找到这种模式的可能性可能会更高

另一个需要考虑的问题是,如果我们开始向spec/编译器添加特殊情况,那么理解、解释和维护就会变得更加困难


我已经提交了这个bug报告:

您可以使用
c显式调用第一个方法。应用(x->System.out.println(x))。但是它看起来没有它应该可以工作…我猜答案是B的子类型T可能实现A。@user2580516是的,这可能是问题所在,我没有想到这种可能性。B的子类型T可能实现A,但是它仍然不是一个功能接口。相关:正如@StuartMarks评论的那样,实现a的B的子类型不是一个功能接口。当我试图将lambda传递给使用这种类型参数化的方法时,编译器报告了一个错误
    comparing(Function)
    comparingDouble(ToDoubleFunction)
    comparingInt(ToIntFunction)
    comparingLong(ToLongFunction)
public class C {
    public void apply(A x) { }    
    public <T extends B> T apply(T x) { return x; }
}