变量已经在方法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具有上述所有内容的相同范围,所以这失败了。