提问人:ryvantage 提问时间:4/1/2014 最后编辑:ZhekaKozlovryvantage 更新时间:8/10/2019 访问量:25158
变量已在方法 lambda 中定义
Variable is already defined in method lambda
问:
考虑以下几乎可以编译的 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();
变量 user 已在方法 main(String[]) 中定义
我的问题是:为什么 Lambda 表达式将正在初始化的变量与已定义的 Lambda 表达式在同一行上?我知道 Lambda 在自身外部查找(并使用)局部变量,因此您不能将 Lambda 内部使用的变量命名为与外部变量相同的变量。但是,为什么正在定义的变量被认为是已经定义的呢?
答:
查看代码
User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();
变量名称为 ,lambda 中的变量也是user
user
尝试将其更改为如下所示
User user = users.stream().filter((otherUser) -> otherUser.getId() == 1).findAny().get();
评论
user
user
它与任何其他局部变量相同:您不允许在更多的内部 {} 块中隐藏它们。
让我们去看一下 Java 语言规范中关于名称及其作用域的信息
方法的形式参数的作用域 (§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 范围内声明一个新变量,除非 变量在声明位于 v.
所以,在
User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();
,变量的作用域是该块中它后面的所有内容。现在,您正在尝试使用该变量的名称在作用域内声明一个新变量,但不是user
在声明在 v 范围内的类中。
因此,会发生编译时错误。(它是在 lambda 表达式中声明的,而不是在类中声明的。
这个问题很老了,但我认为我的答案可以使已经给出的答案更加清晰。特别是对@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) 引用。
某些声明不允许在局部变量、形式参数、异常参数或局部类声明的范围内进行,因为仅使用简单名称无法区分声明的实体。
因为 lambda 与上面提到的所有事情具有相同的范围,所以这失败了。
评论
User user
BigDecimal x = x.plus(y)
user
请注意,此限制将在将来的版本中删除。引用 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 声明的局部变量)对封闭范围内定义的变量进行影子。(一个可能反对的论点是可读性:如果允许 lambda 参数进行遮蔽,那么在上面的例子中,标识符“key”在使用它的两个地方表示两种不同的东西,并且似乎没有语法障碍来区分这两种用法。
评论
user
user
user
user
user
user
user
int x=x+1;
x
x
x
x