据我所知,在Haskell等语言中,以及作为lambda演算的一部分,每个lambda表达式都有自己的作用域,所以如果我有嵌套的lambda表达式,例如:\x -> (\x -> x)
那么第一个{{ 1}}参数与第二个\x
不同。
在Java中,如果执行此操作,则会出现编译错误,就像您再次使用\x
作为参数名称或lambda中的局部变量名称一样,如果已在封闭范围内使用,例如作为方法参数。
有没有人知道为什么Java以这种方式实现了lambda表达式 - 为什么不让它们引入一个新的范围并且表现得像一个匿名类呢?我假设是因为某些限制或优化,或者可能是因为lambdas必须被黑客入侵现有语言?
答案 0 :(得分:12)
这与Java中其他代码块的行为相同。
这会产生编译错误
int a;
{
int a;
}
虽然这不是
{
int a;
}
{
int a;
}
您可以在section 6.4 of the JLS中阅读有关此主题的内容以及一些推理。
答案 1 :(得分:4)
lambda块是一个新的块,也就是范围,但它没有像匿名类实现那样建立新的上下文/级别。
来自Java语言规范15.27.2 Lambda Body:
与匿名类声明中出现的代码不同,名称的含义以及出现在lambda主体中的
this
和super
关键字以及引用声明的可访问性是相同在周围的上下文(除了lambda参数引入新名称)。
来自JLS 6.4 Shadowing and Obscuring:
这些规则允许重新声明变量或嵌套类声明中的本地类(本地类(§14.3)和<强>匿名类(§15.9))发生在变量或本地类的范围内。因此,形式参数,局部变量或局部类的声明可以嵌套在嵌套在方法,构造函数或lambda表达式中的类声明中;并且异常参数的声明可以嵌套在嵌套在catch子句块中的类声明中。
有两种设计方案可以处理由lambda参数和lambda表达式中声明的其他变量创建的名称冲突。一种是模仿类声明:就像本地类一样,lambda表达式为名称引入了一个新的“级别”,表达式之外的所有变量名都可以重新声明。另一种是“本地”策略:如catch子句,for循环和块,lambda表达式在与封闭上下文相同的“级别”操作,并且表达式外部的局部变量不能被遮蔽。 以上规则使用本地策略;没有特殊的分配允许在lambda表达式中声明的变量遮蔽在封闭方法中声明的变量。
示例:
class Test {
private int f;
public void test() {
int a;
a = this.f; // VALID
{
int a; // ERROR: Duplicate local variable a
a = this.f; // VALID
}
Runnable r1 = new Runnable() {
@Override
public void run() {
int a; // VALID (new context)
a = this.f; // ERROR: f cannot be resolved or is not a field
// (this refers to the instance of Runnable)
a = Test.this.f; // VALID
}
};
Runnable r2 = () -> {
int a; // ERROR: Lambda expression's local variable a cannot redeclare another local variable defined in an enclosing scope.
a = this.f; // VALID
};
}
}
答案 2 :(得分:1)
Java中的Lambda引入了新的作用域-在Lambda中声明的任何变量只能在Lambda中访问。
您真正要问的是 shadowing -更改已经绑定到某个外部范围的变量的绑定。
允许一定程度的阴影化是合乎逻辑的:您希望能够用本地名称来阴影全局名称,因为否则,您可以通过向某个全局名称空间添加新名称来破坏本地代码。为了简单起见,很多语言只是将规则扩展到本地名称。
另一方面,重新绑定本地名称是一种代码味道,并且可能是一些细微错误的来源,而同时-不提供任何技术优势。既然您提到了Haskell,就可以看看this discussion on Lambda the Ultimate。
这就是为什么Java禁止对局部变量进行阴影处理(就像其他许多潜在的危险事物一样),但允许对局部变量进行阴影处理(以便添加属性不会破坏已经使用该名称的方法)。
因此,Java 8的设计者必须回答一个问题,即lambda是否应表现得更像代码块(不带阴影)或像内部类(带阴影),并做出有意识的决定将它们像前者一样对待。
答案 3 :(得分:0)
虽然其他答案看起来像是语言设计者的明确决定,但实际上有一个 JEP 建议为 lambda 参数引入阴影(重点是我的):
<块引用>Lambda 参数不允许在封闭的变量中隐藏变量 范围。 [...] 最好取消此限制,并且 允许 lambda 参数(和用 lambda 声明的局部变量)隐藏 在封闭作用域中定义的变量。
该提案相对较旧,显然还没有找到进入 JDK 的途径。但由于它还包括对下划线的更好处理(在 Java 8 中不推荐将其作为标识符为这种处理铺平道路),我可以想象整个提案并没有完全脱离桌面。