Java 变量已在方法lambda中定义
考虑以下几乎可编译的Java 8代码:Java 变量已在方法lambda中定义,java,lambda,java-8,Java,Lambda,Java 8,考虑以下几乎可编译的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().filt
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;
}
}
publicstaticvoidmain(字符串[]args){
LinkedList用户=null;
添加(新用户(1,“用户1”);
添加(新用户(2,“用户2”);
添加(新用户(3,“用户3”);
User User=users.stream().filter((User)->User.getId()==1.findAny().get();
}
静态类用户{
int-id;
字符串用户名;
公共用户(){
}
公共用户(int-id,字符串用户名){
this.id=id;
this.username=用户名;
}
public void setUsername(字符串用户名){
this.username=用户名;
}
公共无效集合id(内部id){
this.id=id;
}
公共字符串getUsername(){
返回用户名;
}
公共int getId(){
返回id;
}
}
您会注意到User User=users.stream().filter((User)->User.getId()=1.findAny().get()代码>引发编译器错误:
变量user已在方法main中定义(字符串[])
我的问题是:lambda表达式为什么要考虑与已经定义的lambda表达式在同一行上初始化的变量?我理解Lambda在外部寻找(并使用)局部变量,因此不能将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语言规范
方法的形式参数的范围(§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在上述所有内容中的作用域相同,因此此操作失败。注意,此限制将在未来的版本中删除。引自:
不允许Lambda参数在封闭作用域中对变量进行阴影处理。(换句话说,lambda的行为类似于for语句-请参阅JLS)这通常会导致问题,如以下(非常常见)情况:
Map msi=。。。
...
String key=computeSomeKey();
msi.computeIfAbsent(key,key->key.length())//错误
在这里,在ComputeFabSent调用中将name键重新用作lambda参数的尝试失败,因为已在封闭上下文中定义了具有相同名称的变量
最好取消此限制,并允许lambda参数(以及使用lambda声明的局部变量)对封闭作用域中定义的变量进行阴影处理。(一个可能反对的论点是可读性:如果允许lambda参数进行阴影处理,那么在上面的示例中,标识符“key”在使用它的两个地方意味着两种不同的东西,并且似乎没有语法障碍来区分这两种用法。)
声明发生在初始化之前。局部变量user
是在user
在lambda表达式中之前定义的。@MirroredFate我认为关键是lambda表达式user
参数实际上应该在完全不同的上下文(表达式的计算上下文)中使用,那么为什么它会受到外部定义的user
变量的影响呢?目前我能想到的唯一论点是,变量user
是闭包环境的一部分,它已经在lambda表达式的上下文中,因此,如果声明另一个变量user
,则存在名称冲突。但是我还是
Map<String, Integer> msi = ...
...
String key = computeSomeKey();
msi.computeIfAbsent(key, key -> key.length()) //error