为什么java lambda表达式不会引入新的范围?

时间:2016-04-29 15:31:29

标签: java function lambda expression lambda-calculus

据我所知,在Haskell等语言中,以及作为lambda演算的一部分,每个lambda表达式都有自己的作用域,所以如果我有嵌套的lambda表达式,例如:\x -> (\x -> x)那么第一个{{ 1}}参数与第二个\x不同。

在Java中,如果执行此操作,则会出现编译错误,就像您再次使用\x作为参数名称或lambda中的局部变量名称一样,如果已在封闭范围内使用,例如作为方法参数。

有没有人知道为什么Java以这种方式实现了lambda表达式 - 为什么不让它们引入一个新的范围并且表现得像一个匿名类呢?我假设是因为某些限制或优化,或者可能是因为lambdas必须被黑客入侵现有语言?

4 个答案:

答案 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主体中的thissuper关键字以及引用声明的可访问性是相同在周围的上下文(除了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 中不推荐将其作为标识符为这种处理铺平道路),我可以想象整个提案并没有完全脱离桌面。