变量已经在方法lambda中定义

时间:2014-03-31 21:56:52

标签: 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().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中使用的变量命名为外部变量。但为什么 定义的变量被视为已经定义?

5 个答案:

答案 0 :(得分:14)

让我们转到names and their scopes

上的Java语言规范
  

方法(第8.4.1节)的形式参数的范围,构造函数   (§8.8.1),或lambda表达式(§15.27)是整个身体   方法,构造函数或lambda表达式。

     

块(第14.4节)中局部变量声明的范围是   声明出现的块的其余部分,从它开始   拥有初始值设定项并包含右侧的任何其他声明符   本地变量声明声明。

然后,关于shadowing and obscuring

的主题
  

局部变量(§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与上面提到的所有内容具有相同的范围,所以这会失败。