Java 变量已在方法lambda中定义

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

考虑以下几乎可编译的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;
    }
}
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