【发布时间】:2014-06-19 07:09:23
【问题描述】:
好的,所以方法重载是坏事™。现在这已经解决了,让我们假设我实际上想要重载这样的方法:
static void run(Consumer<Integer> consumer) {
System.out.println("consumer");
}
static void run(Function<Integer, Integer> function) {
System.out.println("function");
}
在 Java 7 中,我可以使用明确的匿名类作为参数轻松调用它们:
run(new Consumer<Integer>() {
public void accept(Integer integer) {}
});
run(new Function<Integer, Integer>() {
public Integer apply(Integer o) { return 1; }
});
现在在 Java 8 中,我当然想用 lambda 表达式调用这些方法,而且我可以!
// Consumer
run((Integer i) -> {});
// Function
run((Integer i) -> 1);
既然编译器应该能够推断出Integer,那我为什么不把Integer放开呢?
// Consumer
run(i -> {});
// Function
run(i -> 1);
但这不能编译。编译器(javac,jdk1.8.0_05)不喜欢这样:
Test.java:63: error: reference to run is ambiguous
run(i -> {});
^
both method run(Consumer<Integer>) in Test and
method run(Function<Integer,Integer>) in Test match
对我来说,凭直觉,这没有意义。产生返回值(“值兼容”)的 lambda 表达式和产生 void(“void-compatible”)的 lambda 表达式之间绝对没有歧义,如 JLS §15.27 中所述。
当然,JLS 是深刻而复杂的,我们继承了 20 年的向后兼容历史,还有一些新的东西,比如:
适用性测试会忽略包含隐式类型 lambda 表达式 (§15.27.1) 或不精确方法引用 (§15.13.1) 的某些参数表达式,因为它们的含义在选择目标类型之前无法确定。
上述限制可能与JEP 101没有一路实现有关,如here和here所示。
问题:
谁能准确告诉我 JLS 的哪些部分指定了这种编译时歧义(或者是编译器错误)?
奖励:为什么事情会这样决定?
更新:
使用 jdk1.8.0_40,上面的编译和工作正常
【问题讨论】:
-
@SyamS:
i是Consumer.accept()或Function.apply()的第一个(也是唯一一个)参数。这本身可能是模棱两可的。但鉴于一个 lambda 评估为“值兼容”类型(Function),另一个评估为“无效兼容”类型(Consumer),我直觉上认为没有歧义 -
对不起,如果这听起来很傻,但函数重载通常只取决于输入类型。它不检查返回类型。所以在这种情况下,accept 和 apply 都接受一个整数类型的参数。所以对我来说它看起来模棱两可。 :) lambda 是否寻找返回类型进行推理?
-
它确实适用于早期版本(例如
beta 102和更早版本)。 -
@SyamS 这似乎有点模棱两可,但编译器可以确定
run((Integer i) -> {})是消费者。因此,尽管它可能是 Function或 Consumer ,但 Consumer 是最佳匹配,编译器会使用它。问题是,为什么编译器只在您指定 (Integer i)而不仅仅是i时才这样做。 -
@SyamS:
i -> {}永远不能评估为Function,因为它是“无效兼容的”。i -> 1永远无法评估为Consumer,因为它是“值兼容的”。在我看来,对于每个调用,只有一个重载方法甚至是适用。正如@jacobhyphenated 还指出的那样,可以通过显式指定相同函数参数类型(Integer i)来解决歧义。
标签: java lambda java-8 overloading jls