变量已经在方法lambda中定义

考虑以下几乎可编译的Java 8代码:

public static void main(String[] args) { LinkedList users = null; users.add(new User(1, "User1")); users.add(new User(2, "User2")); users.add(new User(3, "User3")); User user = users.stream().filter((user) -> user.getId() == 1).findAny().get(); } static class User { int id; String username; public User() { } public User(int id, String username) { this.id = id; this.username = username; } public void setUsername(String username) { this.username = username; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public int getId() { return id; } } 

你会注意到User user = users.stream().filter((user) -> user.getId() == 1).findAny().get(); 抛出编译器错误:

变量用户已在方法main(String [])中定义

我的问题是:为什么Lambda表达式会考虑与已定义的Lambda表达式在同一行上初始化的变量? 我理解Lambdas在外部查找(并使用)局部变量,因此您不能将Lambda中使用的变量命名为外部变量。 但是为什么定义的变量已定义?

让我们来看看关于名称及其范围的Java语言规范

方法(第8.4.1节),构造函数(第8.8.1节)或lambda表达式(第15.27节)的forms参数的范围是方法,构造函数或lambda表达式的整个主体。

块(第14.4节)中局部变量声明范围是声明出现的块的其余部分,从其自己的初始化程序开始, 并在本地变量声明语句中包含右侧的任何其他声明

然后,关于阴影和模糊的主题

局部变量(第14.4节),forms参数(第8.4.1节,第15.27.1节),exception参数(第14.20节)和本地类(第14.3节)只能使用简单名称而不是限定名称来引用(第6.2节)。

在局部变量,forms参数,exception参数或本地类声明的范围内不允许某些声明,因为仅使用简单名称来区分声明的实体是不可能的。

如果使用局部变量v的名称来声明v范围内的新变量,则这是一个编译时错误,除非在声明在v的范围内的类中声明新变量。

所以,在

 User user = users.stream().filter((user) -> user.getId() == 1).findAny().get(); 

,变量user的范围是该块之后的所有内容。 现在,您尝试使用该变量的名称在范围内声明一个新变量,但不是

在声明属于v范围内的类中。

所以发生编译时错误。 (它在lambda表达式中声明,而不是在类中声明。)

看看代码

 User user = users.stream().filter((user) -> user.getId() == 1).findAny().get(); 

变量名是user ,lambda中的变量也是user

尝试改变它是这样的

 User user = users.stream().filter((otherUser) -> otherUser.getId() == 1).findAny().get(); 

它与任何其他局部变量相同:不允许在更多内部{}块中遮蔽它们。

请注意,此限制将在未来的版本中删除(我认为在Java 11或Java 12中)。 引自JEP-302 :

不允许Lambda参数影响封闭范围中的变量。 (换句话说,lambda的行为类似于for语句 – 请参阅JLS)这通常会导致问题,如下所示(非常常见):

 Map msi = ... ... String key = computeSomeKey(); msi.computeIfAbsent(key, key -> key.length()) //error 

这里,尝试在computeIfAbsent调用中将name键重用为lambda参数失败,因为已在封闭上下文中定义了具有相同名称的变量。

需要解除此限制,并允许lambda参数(以及使用lambda声明的locals)影响在封闭范围中定义的变量。 (一个可能的反对意见是可读性:如果允许lambda参数为阴影,那么在上面的例子中,标识符’key’在使用它的两个地方意味着两个不同的东西,并且似乎没有单独的语法障碍这两个用法。)

这个问题很老了,但我认为我的答案可以为已经给出的答案增加更清晰的答案。 特别是@Sotirios Delimanolis。 lambda的分配

  User user = users.stream().filter((user) -> user.getId() == 1).findAny().get(); 

因以下代码失败的原因相同。

  Object e = null; try{ throw new Exception(); } catch(Exception e) { // compilation fails because of duplicate declaration //do nothing } 

局部变量(第14.4节),forms参数(第8.4.1节,第15.27.1节),exception参数(第14.20节)和本地类(第14.3节)只能使用简单名称而不是限定名称来引用(第6.2节)。

在局部变量,forms参数,exception参数或本地类声明的范围内不允许某些声明,因为仅使用简单名称来区分声明的实体不可能的

因为lambdas具有上述所有内容的相同范围,所以这失败了。