【问题标题】:Variable is already defined in method lambda变量已在方法 lambda 中定义
【发布时间】:2014-03-31 21:56:52
【问题描述】:

考虑以下几乎可编译的 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) -&gt; user.getId() == 1).findAny().get(); 抛出编译器错误:

变量用户已在方法 main(String[]) 中定义

我的问题是:为什么 Lambda 表达式将初始化的变量与已经定义的 Lambda 表达式放在同一行?我了解 Lambda 在自身之外寻找(并使用)局部变量,因此您不能将在 Lambda 中使用的变量命名为与外部变量相同。但是为什么正在定义的变量被认为已经定义了?

【问题讨论】:

  • 声明发生在初始化之前。您的局部变量 useruser 之前定义在 lambda 表达式中。
  • @MirroredFate 我认为关键在于 lambda 表达式 user 参数实际上应该在完全不同的上下文(表达式的评估上下文)中使用,那么它为什么会受到影响由外部定义的user 变量?
  • 目前我能想到的唯一论据是变量 user 是闭包环境的一部分,它已经在 lambda 表达式的上下文中,所以,如果你声明另一个变量user,存在名称冲突。但是,我仍然想知道,为什么它没有被简单地遮住。这可能与事物在引擎盖下的仪表化方式有关吗?
  • @EdwinDalorzo 他的问题是为什么正在定义的变量被认为已经定义?,我的评论试图解释这一点。
  • 您混淆了声明和初始化。变量在初始化之前声明。对于初始化程序中的 lambda,变量 user 已声明,但尚未初始化。这对于没有 lambdas 的局部变量是一样的,例如对于int x=x+1;,第二个x 将引用变量x,该变量已经声明,但引用无效,因为x 那时还没有初始化 .不会尝试在外部范围内找到已初始化的 x

标签: java lambda java-8


【解决方案1】:

让我们转到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 表达式中声明的,而不是在类中。)

【讨论】:

    【解决方案2】:

    看代码

    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();
    

    【讨论】:

    • 嗯,我认为 OP 知道这一点
    • 是的,我认为问题是为什么 lambda user 变量不会在闭包上下文中隐藏 user 变量?否则这个问题很无聊。
    【解决方案3】:

    这与任何其他局部变量相同:不允许将它们隐藏在更多内部 {} 块中。

    【讨论】:

      【解决方案4】:

      请注意,此限制将在未来的版本中删除。引用JEP-302:

      Lambda 参数不允许在封闭范围内隐藏变量。 (换句话说,lambda 的行为类似于 for 语句 - 请参阅 JLS)这通常会导致问题,如以下(非常常见的)情况:

      Map<String, Integer> msi = ...
      ...
      String key = computeSomeKey();
      msi.computeIfAbsent(key, key -> key.length()) //error
      

      这里,尝试在 computeIfAbsent 调用中重用名称键作为 lambda 参数失败,因为已在封闭上下文中定义了具有相同名称的变量。

      最好取消这个限制,并允许 lambda 参数(以及用 lambda 声明的局部变量)隐藏在封闭范围中定义的变量。 (一个可能的反对理由是可读性:如果允许 lambda 参数隐藏,那么在上面的示例中,标识符 'key' 在使用它的两个地方意味着两个不同的东西,并且似乎没有语法障碍来分隔两种用法。)

      【讨论】:

      • 这很酷。不过,与手头的主题的相关性稍差,因为我的示例中的标识符 并不 意味着两个不同的东西,因为外部变量 被 lambda 初始化与声明相同的行。换句话说,外部变量只能在 lambda 完成后使用,从而创建所述缺失的语法障碍。
      【解决方案5】:

      这个问题已经很老了,但我认为我的回答可以使已经给出的答案更加清晰。特别是@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 节)。

      某些声明不允许在局部变量、形参、异常参数或局部类声明的范围内,因为无法仅使用简单名称区分声明的实体

      因为 lambdas 与上面提到的所有东西的作用域相同,所以这会失败。

      【讨论】:

      • “无法区分”我不同意。正在初始化User user 肯定是不可用的(例如,BigDecimal x = x.plus(y) 显然是错误的)。因此,说 lambda 中的 user 变量显然不是指当前正在初始化的变量,这似乎并不过分。因此编译器应该能够授予您使用相同名称的能力。
      • 从逻辑上讲,你是对的。但是 java 语义只是不在 lambdas 或任何声明中使用这种推断。 java 编译器无法区分前向引用和 lambda 参数。顺便说一句,无法区分是 JLS,如果那是您对答案的反对意见。
      • 老兄,我知道。这个问题清楚地表明我理解你不能,但是为什么他们特别决定使变量名的范围如此具体,以排除没有冲突的明显情况仍然是一个谜。再一次,很明显,他们特别决定制定这个规范,但似乎没有明显的原因。您的回答没有增加任何新内容。唯一要提供的新答案是编译器通过限制您来防止错误的情况。
      • 你能想到这样一种情况,如果你被允许将一个内部 lambda 变量命名为与正在初始化的变量相同的东西,它会导致运行时错误吗?
      • 因为 lambda 只是另一个块,它没有自己的上下文。 15.27.2 Lambda Body 部分指定 与匿名类声明中出现的代码不同,名称的含义以及出现在 lambda 主体中的 thissuper 关键字,以及引用声明的可访问性,与周围上下文中的相同(除了 lambda 参数引入了新名称)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-18
      • 2018-03-20
      • 2015-06-09
      • 1970-01-01
      • 2022-06-22
      • 1970-01-01
      相关资源
      最近更新 更多