Java Lambda表达式和方法重载
好的,所以方法重载是一件坏事™. 现在已经解决了这个问题,让我们假设我真的想重载一个如下的方法:Java Lambda表达式和方法重载,java,lambda,java-8,overloading,jls,Java,Lambda,Java 8,Overloading,Jls,好的,所以方法重载是一件坏事™. 现在已经解决了这个问题,让我们假设我真的想重载一个如下的方法: static void run(Consumer<Integer> consumer) { System.out.println("consumer"); } static void run(Function<Integer, Integer> function) { System.out.println("function&q
static void run(Consumer<Integer> consumer) {
System.out.println("consumer");
}
static void run(Function<Integer, Integer> function) {
System.out.println("function");
}
既然编译器应该能够推断出整数
,那么我为什么不把整数
放在一边呢
// Consumer
run(i -> {});
// Function
run(i -> 1);
但这并没有编译。编译器(javac,jdk1.8.0_05)不喜欢这样:
Test.java:63:error:run的引用不明确
运行(i->{});
^
这两种方法都在测试和测试中运行(使用者)
测试匹配中的方法运行(函数)
直觉上,这对我来说毫无意义。产生返回值(“值兼容”)的lambda表达式与产生void
(“void兼容”)的lambda表达式之间绝对没有歧义,如中所述
但当然,JLS是深刻而复杂的,我们继承了20年的向后兼容性历史,并且有如下新事物:
适用性测试将忽略某些包含隐式类型lambda表达式()或不精确方法引用()的参数表达式,因为在选择目标类型之前无法确定其含义
上述限制可能与未完全实现的事实有关,如图所示和所示
问题:
谁能确切地告诉我JLS的哪些部分指定了这种编译时模糊性(或者它是编译器错误)
奖金:为什么事情是这样决定的
更新:
使用jdk1.8.0_40,以上代码可以很好地编译和工作我想您已经找到了()
与之相比:
- 如果满足以下所有条件,lambda表达式(§15.27)可能与函数接口类型(§9.8)兼容:
- 目标类型的函数类型的arity与lambda表达式的arity相同
- 如果目标类型的函数类型具有void返回,则lambda主体是语句表达式(§14.8)或void兼容块(§15.27.2)
- 如果目标类型的函数类型具有(非void)返回类型,则lambda主体是表达式或值兼容块(§15.27.2)
void
兼容块”和“值兼容块”之间的明确区别。虽然在某些情况下块可能同时是两个,但该部分清楚地指出,像()->{}
这样的表达式是“void
兼容块”,因为它正常完成而不返回值。显然,i->{}
也是一个“void
兼容块”
并且根据上面引用的部分,lambda与值不兼容的块以及目标类型与(非void
)返回类型的组合不是方法重载解析的潜在候选。所以你的直觉是对的,这里不应该有歧义
不明确块的示例包括
() -> { throw new RuntimeException(); }
() -> { while (true); }
因为它们不能正常完成,但您的问题不是这样。假设我们有方法和方法调用
void run(Function<Integer, Integer> f)
run(i->i)
这会导致编译器错误,因为run(..)和run(..)具有相同的擦除。因此,由于JVM不能支持具有相同名称和参数类型的两个函数,因此无法编译这两个函数。因此,编译器永远不必解决此类场景中的歧义,因为由于Java类型系统中预先存在的规则,它们被明确禁止
这就给我们留下了参数arity为1的其他函数类型
void run(IntUnaryOperator f)
此处run(i->i)
对函数和IntUnaryOperator
都有效,但这将拒绝编译,因为运行的引用不明确
,因为两个函数都匹配此lambda。事实上,他们确实是这样做的,这里的错误是意料之中的
interface X { void thing();}
interface Y { String thing();}
void run(Function<Y,String> f)
void run(Consumer<X> f)
run(i->i.thing())
这里我们知道两种函数类型都有一个Integer
参数,因此我们知道i->
中的i
必须是Integer
。所以我们知道必须调用run(Function)
。但是编译器没有尝试这样做。这是编译器第一次执行我们不期望的操作
为什么不这样做呢?我这么说是因为这是一个非常特殊的情况,在这里推断类型需要我们在上述任何其他情况下都没有见过的机制,因为在一般情况下,他们无法正确推断类型并选择正确的方法。JDK错误系统中已经报告了此错误:。正如你所能检查的那样,这个bug已经被修复了。此修复程序在这方面将javac与规范同步。现在javac正确地接受了带有隐式lambdas的版本。要获取此更新,您需要克隆
修复程序所做的是分析lambda主体,并确定其是否为void或值兼容。要确定这一点,您需要分析所有返回语句。让我们记住,上述规范(15.27.2)中已经提到:
- 如果块中的每个返回语句
该块具有返回表单
- 如果无法完成,则块lambda主体与值兼容
通常()和块中的每个return语句都具有
表单返回表达式
这意味着,通过分析lambda主体中的返回,您可以知道lambda主体是否与void兼容,但要确定其是否与值兼容,您还需要对其进行流分析,以确定它是否可以正常完成()
对于正文既不是void也不是值兼容的情况,此修复程序还引入了一个新的编译器错误,例如,如果我们编译以下代码:
class Test {
interface I {
String f(String x);
}
static void foo(I i) {}
void m() {
foo((x) -> {
if (x == null) {
return;
} else {
return x;
}
});
}
}
编译器将给出以下输出:
Test.java:9: error: lambda body is neither value nor void compatible
foo((x) -> {
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
我希望这能有所帮助。@SyamS:I
是Consumer.accept()
或Function.apply()的第一个(也是唯一一个)参数。这本身可能是含糊不清的
void run(IntUnaryOperator f)
interface X { void thing();}
interface Y { String thing();}
void run(Function<Y,String> f)
void run(Consumer<X> f)
run(i->i.thing())
void run(Consumer<Integer> f)
void run(Function<Integer,Integer> f)
run(i->i)
class Test {
interface I {
String f(String x);
}
static void foo(I i) {}
void m() {
foo((x) -> {
if (x == null) {
return;
} else {
return x;
}
});
}
}
Test.java:9: error: lambda body is neither value nor void compatible
foo((x) -> {
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error