【问题标题】:java: Is it possible to set a lambda expression for an array of Buttons is a for loop? If so how?java:是否可以为按钮数组设置 lambda 表达式是一个 for 循环?如果有怎么办?
【发布时间】:2023-04-06 09:16:01
【问题描述】:

我希望能够做这样的事情:

for(i = 0; i < 10; i++) {
    //if any button in the array is pressed, disable it.
    button[i].setOnAction( ae -> { button[i].setDisable(true) } );
}

但是,我收到一条错误消息 "local variables referenced from a lambda expression must be final or effectively final"。我怎么可能仍然做类似上面的代码的事情(如果可能的话)?如果做不到,应该怎么做才能得到类似的结果?

【问题讨论】:

  • 您应该能够从ActionEvent 获取按钮,而不是另一个数组查找。

标签: java javafx


【解决方案1】:

正如错误消息所说,从 lambda 表达式引用的局部变量必须是最终的或有效最终的(“有效最终”意味着编译器可以为您使其成为最终的)。

简单的解决方法:

for(i = 0; i < 10; i++) {
    final int ii = i;
    button[i].setOnAction( ae -> { button[ii].setDisable(true) } );
}

【讨论】:

  • 行得通,谢谢。我来自 C++ 编程背景,Java 中这样的事情有时会让我感到厌烦……就像比较字符串时一样。感谢您提供快速准确的答复。
  • 您也可以引用button[i] 并重复使用它。 lambda 中的Button current = button[i]current.setDisable(true);
  • 如果你很好奇为什么:变量值被 按值 传递到 lambda 中,我猜他们想留下通过引用传递非最终变量的可能性更高版本。它还避免了 () -&gt; {i = 5;} 实际上不会更改 i 的混淆(如果允许该 lambda 而 i 是最终的)。
【解决方案2】:

由于您使用的是 lambda,您还可以从 Java 8 的其他功能(例如流)中受益。

例如IntStream:

支持顺序和并行聚合操作的原始 int 值元素序列。这是 Stream 的 int 原始特化。

可以用来代替for循环:

IntStream.range(0,10).forEach(i->{...});

所以现在你有一个可以用于你的目的的索引:

IntStream.range(0,10)
         .forEach(i->button[i].setOnAction(ea->button[i].setDisable(true)));

您还可以从数组生成流:

 Stream.of(button).forEach(btn->{...});

在这种情况下,您将没有索引,因此正如@shmosel 建议的那样,您可以使用事件的来源:

Stream.of(button)
          .forEach(btn->btn.setOnAction(ea->((Button)ea.getSource()).setDisable(true)));    

编辑

正如@James_D 所建议的,这里没有必要低调:

Stream.of(button)
      .forEach(btn->btn.setOnAction(ea->btn.setDisable(true)));

在这两种情况下,您还可以从并行操作中受益:

IntStream.range(0,10).parallel()
         .forEach(i->button[i].setOnAction(ea->button[i].setDisable(true)));

Stream.of(button).parallel()
          .forEach(btn->btn.setOnAction(ea->btn.setDisable(true)));

【讨论】:

  • Stream.of(button).forEach(...) 示例不需要在事件处理程序中获取动作事件的来源。变量btn 在那里(所以你可以避免丑陋的沮丧)。
  • JavaFx 节点上的并行操作?我认为所有操作都需要在 fx 线程上进行
  • @Adam from the node Javadoc: "节点对象可以在任何线程上构建和修改,只要它们尚未附加到场景。应用程序必须将节点附加到场景,并修改JavaFX 应用程序线程上已经附加到场景的节点。”不过,只需 10 个按钮,streaming the setOnAction in parallel is of questionable value
  • @jewelsea 如果我错了,请纠正我,但在这种情况下,我们只是将事件处理程序添加到节点集合中,这不会影响它们的呈现。即使将节点附加到场景也可以完成。这与在单击时禁用节点的操作无关,该操作将在 JavaFX 应用程序线程上执行。当然,如果节点已经附加到场景中,与渲染相关的操作将会失败。
  • 何塞,这种操作的并行代码可能会很好地工作,但在大多数情况下,我不会推荐它。仍然存在轻微(尽管不太可能)的风险,即它可能会同时产生副作用(例如 onAction 是一个属性,有人可以在其上设置访问场景图的侦听器)。此外,除非您设置约 10000 个动作侦听器,否则并行化设置器的执行速度将比顺序代码慢。可能是另一个堆栈溢出问题或询问openjfx-dev mailing list 的好话题。
【解决方案3】:

使用Event 获取源Node

for(int i = 0; i < button.length; i++)
{
     button[i].setOnAction(event ->{
         ((Button)event.getSource()).setDisable(true);
     });
}

【讨论】:

    【解决方案4】:

    Lambda 表达式实际上就像是在流上工作的匿名方法。为了避免任何不安全的操作,Java 规定不能在 lambda 表达式中访问任何可修改的外部变量。

    为了解决这个问题,

    最终的 int 索引=按钮[i]; 并在 lambda 表达式中使用 index 而不是 i

    【讨论】:

      【解决方案5】:

      您说如果按下按钮,但在您的示例中,列表中的所有按钮都将被禁用。尝试为每个按钮关联一个监听器,而不是仅仅禁用它。

      对于逻辑,你的意思是这样的吗:

      Arrays.asList(buttons).forEach(
              button -> button.addActionListener(new ActionListener() {
      
                  @Override
                  public void actionPerformed(ActionEvent e) {
                      button.setEnabled(false);
      
                  }
      }));
      

      我也喜欢 Sedrick 的回答,但您必须在循环内添加一个动作侦听器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-07-31
        • 2014-08-17
        • 1970-01-01
        • 2013-12-19
        • 1970-01-01
        相关资源
        最近更新 更多