从方法参数中捕获 Java 8 lambda 可变变量?

标签 java lambda closures

我正在使用 AdoptOpenJDK jdk81212-b04在 Ubuntu Linux 上,在 Eclipse 4.13 上运行。我在 Swing 中有一个在 lambda 中创建 lambda 的方法;两者都可能在不同的线程上被调用。它看起来像这样(伪代码):

private SwingAction createAction(final Data payload) {
  System.out.println(System.identityHashCode(payload));
  return new SwingAction(() -> {
    System.out.println(System.identityHashCode(payload));
    //do stuff
    //show an "are you sure?" modal dialog and get a response
    //show a file selection dialog
    //when the dialog completes, create a worker and show a status:
    showStatusDialogWithWorker(() -> new SwingWorker() {
      protected String doInBackground() {
        save(payload);
      }
    });

您可以看到 lambda 有好几层深,最终捕获的“有效负载”或多或少会保存到文件中。

但在考虑层和线程之前,让我们直接进入问题:
  • 第一次调用createAction() , 两个System.out.println()方法打印完全相同的哈希码,表明 lambda 内捕获的有效负载与我传递给 createAction() 的相同.
  • 如果我稍后调用 createAction()使用不同的有效负载,两个 System.out.println()打印的值不同!特别是,打印的第二行始终表示与步骤 1 中打印的值相同!!

  • 我可以一遍又一遍地重复这个;实际传递的有效载荷将不断获得不同的身份哈希码,而打印的第二行(在 lambda 内)将保持不变!最终某些东西会点击,突然数字会再次相同,但随后它们会因相同的行为而发散。

    Java 是否以某种方式缓存了 lambda,以及将要传递给 lambda 的参数?但这怎么可能? payload参数标记为 final ,此外,无论如何,lambda 捕获必须是有效的最终!
  • 如果捕获的变量有几个 lambdas 深,是否存在不识别 lambdas 的 Java 8 错误?
  • 是否存在跨线程缓存 lambda 和 lambda 参数的 Java 8 错误?
  • 或者关于 lambda 捕获方法参数与方法局部变量有什么我不明白的地方吗?

  • 第一次失败的变通方法尝试

    昨天我以为我可以通过在方法堆栈上本地捕获方法参数来防止这种行为:

    private SwingAction createAction(final Data payload) {
      final Data theRealPayload = payload;
      System.out.println(System.identityHashCode(theRealPayload));
      return new SwingAction(() -> {
        System.out.println(System.identityHashCode(theRealPayload));
        //do stuff
        //show an "are you sure?" modal dialog and get a response
        //show a file selection dialog
        //when the dialog completes, create a worker and show a status:
        showStatusDialogWithWorker(() -> new SwingWorker() {
          protected String doInBackground() {
            save(theRealPayload);
          }
        });
    

    用那一行Data theRealPayload = payload , 如果我以后使用 theRealPayload而不是 payload突然bug不再出现,每次我调用createAction() ,两条打印线表示捕获变量的完全相同的实例。

    但是,今天这种解决方法已停止工作。

    单独的错误修复解决问题;但为什么?

    我发现了一个单独的错误,它在 showStatusDialogWithWorker() 中引发了异常。 .基本上 showStatusDialogWithWorker()应该创建工作人员(在传递的 lambda 中)并显示一个状态对话框,直到工作人员完成。有一个错误可以正确创建工作程序,但无法创建对话框,抛出一个会冒泡并且永远不会被捕获的异常。我修复了这个错误,以便 showStatusDialogWithWorker()在工作人员运行时成功显示对话框,然后在工作人员完成后将其关闭。我现在无法再重现 lambda 捕获问题。

    但是为什么showStatusDialogWithWorker()里面的东西与问题有关吗?当我打印出 System.identityHashCode()在 lambda 的外部和内部,并且值不同,这发生在 showStatusDialogWithWorker() 之前正在被调用,并且在抛出异常之前。为什么以后的异常(exception)会有所作为?

    此外,基本问题仍然存在:final 怎么可能?由方法传递并由 lambda 捕获的参数可能会改变吗?

    最佳答案

    how is it even possible that a final parameter passed by a method and captured by a lambda could ever change?



    它不是。正如您所指出的,除非 JVM 中存在错误,否则这不会发生。

    如果没有最小的可重现示例,这很难确定。以下是您所做的观察:

    1. The first time I call createAction(), the two System.out.println() methods print the exact same hash code, indicating that the captured payload inside the lambda is the same I passed to createAction().
    2. If I later call createAction() with a different payload, the two System.out.println() values printed are different! In particular, the second line printed always indicates the same value that was printed in step 1!!


    一个符合证据的可能解释是,第二次调用的 lambda 实际上是第一次运行的 lambda,而第二次运行创建的 lambda 已被丢弃。这将给出上述观察结果,并将错误置于您未在此处显示的代码中。

    也许您可以添加一些额外的日志记录:
    a)在创建时在 createAction 中创建的任何 lambda 的 id(我认为您需要将 lambda 更改为实现回调接口(interface)并在其构造函数中记录的匿名类)
    b) lambda 在调用时的 id

    我认为上述记录足以证明或反驳我的理论。

    GL!

    关于从方法参数中捕获 Java 8 lambda 可变变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59042054/

    相关文章:

    java - JRadioButton 正在选择最后一个

    Java8 Function apply 方法及其实现

    javascript,关于闭包函数调用

    java - 访问现有属性返回 null

    java - 如何检查属性文件路径是否设置正确

    java - 将子类转换为父类(super class)并调用两个类都有的函数

    swift - 将反向地理编码的结果分配给全局变量

    Java 8 lambda 用于为每个部门选择最高薪员工

    c++ - Lambda 中的指针引用

    html5-canvas - Chrome 的非法调用错误和关闭