joh*_*cox 25 java lambda overloading jls java-8
我有一个重载方法,分别接受Consumer和Function对象,并返回一个匹配相应的Consumer/Function的泛型类型.我认为这样会好,但是当我尝试使用lambda表达式调用任一方法时,我得到一个错误,指示对该方法的引用是不明确的.
基于我对JLS§15.12.2.1的阅读.确定可能适用的方法:似乎编译器应该知道我的带有void块的lambda与Consumer方法匹配,而带有返回类型的lambda与Function方法匹配.
我把以下无法编译的示例代码放在一起:
import java.util.function.Consumer;
import java.util.function.Function;
public class AmbiguityBug {
public static void main(String[] args) {
doStuff(getPattern(x -> System.out.println(x)));
doStuff(getPattern(x -> String.valueOf(x)));
}
static Pattern<String, String> getPattern(Function<String, String> function) {
return new Pattern<>(function);
}
static ConsumablePattern<String> getPattern(Consumer<String> consumer) {
return new ConsumablePattern<>(consumer);
}
static void doStuff(Pattern<String, String> pattern) {
String result = pattern.apply("Hello World");
System.out.println(result);
}
static void doStuff(ConsumablePattern<String> consumablePattern) {
consumablePattern.consume("Hello World");
}
public static class Pattern<T, R> {
private final Function<T, R> function;
public Pattern(Function<T, R> function) {
this.function = function;
}
public R apply(T value) {
return function.apply(value);
}
}
public static class ConsumablePattern<T> {
private final Consumer<T> consumer;
public ConsumablePattern(Consumer<T> consumer) {
this.consumer = consumer;
}
public void consume(T value) {
consumer.accept(value);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我还发现了一个类似的 stackoverflow帖子,结果证明是编译器错误.我的情况非常相似,虽然有点复杂.对我来说这仍然看起来像一个bug,但我想确保我不会误解lambdas的语言规范.我正在使用Java 8u45,它应该具有所有最新的修复程序.
如果我将我的方法调用更改为包裹在块中,则所有内容似乎都会编译,但这会增加额外的冗长度,并且许多自动格式化程序会将其重新格式化为多行.
doStuff(getPattern(x -> { System.out.println(x); }));
doStuff(getPattern(x -> { return String.valueOf(x); }));
Run Code Online (Sandbox Code Playgroud)
Tag*_*eev 19
这条线肯定含糊不清:
doStuff(getPattern(x -> String.valueOf(x)));
Run Code Online (Sandbox Code Playgroud)
从链接的JLS章节重读:
如果满足以下所有条件,则lambda表达式(第15.27节)可能与函数接口类型(第9.8节)兼容:
目标类型的函数类型的arity与lambda表达式的arity相同.
如果目标类型的函数类型具有void返回,则lambda主体是语句表达式(§14.8)或void兼容块(§15.27.2).
- 如果目标类型的函数类型具有(非void)返回类型,则lambda主体是表达式或值兼容块(第15.27.2节).
在您的情况下,Consumer您有一个语句表达式,因为任何方法调用都可以用作语句表达式,即使该方法是非void.例如,您可以简单地写这个:
public void test(Object x) {
String.valueOf(x);
}
Run Code Online (Sandbox Code Playgroud)
它毫无意义,但编译完美.您的方法可能有副作用,编译器不知道它.例如,它List.add总是返回true,没有人关心它的返回值.
当然这个lambda也有资格,Function因为它是一个表达式.这就是它的暧昧.如果你有一个表达式而不是语句表达式,那么调用将被映射到Function没有任何问题:
doStuff(getPattern(x -> x == null ? "" : String.valueOf(x)));
Run Code Online (Sandbox Code Playgroud)
当您将其更改为时{ return String.valueOf(x); },您创建一个与值兼容的块,因此它匹配Function,但它不符合与void兼容的块.但是,您也可能遇到块问题:
doStuff(getPattern(x -> {throw new UnsupportedOperationException();}));
Run Code Online (Sandbox Code Playgroud)
此块既可以兼容值,也可以兼容void,因此您会再次出现歧义.另一个ambigue块示例是无限循环:
doStuff(getPattern(x -> {while(true) System.out.println(x);}));
Run Code Online (Sandbox Code Playgroud)
至于System.out.println(x)案例,它有点棘手.它肯定有资格作为语句表达式,因此可以匹配Consumer,但似乎它与表达式匹配,以及spec说方法调用是一个表达式.然而,这是一种有限的使用表达,如15.12.3所说:
如果编译时声明为void,则方法调用必须是顶级表达式(即表达式语句中的表达式或for语句的ForInit或ForUpdate部分),否则会发生编译时错误.这样的方法调用不会产生任何值,因此只能在不需要值的情况下使用.
所以编译器完全遵循规范.首先,它确定你的lambda主体既被限定为表达式(即使它的返回类型为void:15.12.2.1对于这种情况也不例外)和语句表达式,所以它也被认为是歧义.
因此,对我来说,两个语句都根据规范进行编译.ECJ编译器在此代码上生成相同的错误消息.
一般情况下,我建议您在重载具有相同数量的参数时避免重载方法,并且仅在接受的功能接口中有所不同.即使这些功能接口具有不同的arity(例如,Consumer和BiConsumer):lambda也没有问题,但是方法引用可能有问题.在这种情况下,只需为您的方法选择不同的名称(例如,processStuff和consumeStuff).
| 归档时间: |
|
| 查看次数: |
3838 次 |
| 最近记录: |