考虑以下几乎可编译的Java 8代码:
public static void main(String[] args) {
LinkedList<User> 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中使用的变量命名为外部变量。但为什么 定义的变量被视为已经定义?
答案 0 :(得分:14)
的主题方法(第8.4.1节)的形式参数的范围,构造函数 (§8.8.1),或lambda表达式(§15.27)是整个身体 方法,构造函数或lambda表达式。
块(第14.4节)中局部变量声明的范围是 声明出现的块的其余部分,从它开始 拥有初始值设定项并包含右侧的任何其他声明符 本地变量声明声明。
局部变量(§14.4),形式参数(§8.4.1,§15.27.1), 异常参数(§14.20)和本地类(§14.3)只能是 使用简单的名称,而不是限定名称(第6.2节)。
某些声明不允许在本地范围内 变量,形式参数,异常参数或本地类 声明,因为它是不可能区分的 声明的实体只使用简单的名称。
如果使用局部变量v的名称,则为编译时错误 在v的范围内声明一个新变量,除非是新的 变量是在声明在其中的类中声明的 诉讼范围
所以,在
User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();
,变量user
的范围是该块之后的所有内容。现在,您尝试使用该变量的名称在范围内声明一个新变量,但不是
在声明属于v。
范围内的类中
因此发生编译时错误。 (它在lambda表达式中声明,而不是在类中声明。)
答案 1 :(得分:6)
查看代码
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();
答案 2 :(得分:2)
请注意,此限制将在未来版本中删除(我认为在Java 11或Java 12中)。引自JEP-302:
不允许Lambda参数影响封闭范围中的变量。 (换句话说,lambda的行为类似于for语句 - 请参阅JLS)这通常会导致问题,如下所示(非常常见):
Map<String, Integer> msi = ...
...
String key = computeSomeKey();
msi.computeIfAbsent(key, key -> key.length()) //error
这里,尝试在computeIfAbsent调用中将name键重用为lambda参数失败,因为已在封闭上下文中定义了具有相同名称的变量。
最好解除这个限制,并允许lambda参数(以及用lambda声明的locals)遮蔽在封闭范围内定义的变量。 (一个可能的反对意见是可读性:如果允许lambda参数为阴影,那么在上面的例子中,标识符&#39; key&#39;表示在使用它的两个地方有两个不同的东西,似乎有没有语法障碍将两种用法分开。)
答案 3 :(得分:1)
它与任何其他局部变量相同:您不允许在更多内部{}块中遮蔽它们。
答案 4 :(得分:-1)
这个问题很老了,但我认为我的答案可以为已经给出的答案增加更清晰的答案。特别是@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),形式参数(§8.4.1,§15.27.1),异常参数(§14.20)和本地类(§14.3)只能使用简单的名称来引用,而不是合格的名称(§6.2)。
在局部变量,形式参数,异常参数或本地类声明的范围内不允许使用某些声明,因为仅使用简单名称 无法区分声明的实体。 / p>
因为lambdas与上面提到的所有内容具有相同的范围,所以这会失败。