Java Lambda 引用封闭对象:替换为私有静态类?

Posted

技术标签:

【中文标题】Java Lambda 引用封闭对象:替换为私有静态类?【英文标题】:Java Lambda Referencing Enclosing Object: Replace with Private Static Class? 【发布时间】:2016-07-11 07:00:08 【问题描述】:

从其封闭范围引用元素的 Java lambda 持有对其封闭对象的引用。一个人为的例子,lambda 持有对 MyClass 的引用:

class MyClass 
  final String foo = "foo";
  public Consumer<String> getFn() 
    return bar -> System.out.println(bar + foo);
  

如果 lambda 的生命周期很长,这是有问题的;那么我们有一个长期存在的 MyClass 的引用,否则它会超出范围。在这里,我们可以通过将 lambda 替换为私有静态类来进行优化,这样我们只持有对我们需要的 String 的引用,而不是整个类:

class MyClass 

  private static class PrintConsumer implements Consumer<String> 

    String foo;

    PrintConsumer(String foo) 
      this.foo = foo;
    

    @Override
    public void accept(String bar) 
      System.out.println(bar + foo);
    
  

  final String foo = "foo";

  public Consumer<String> getFn() 
    return new PrintConsumer(foo);
  

不幸的是,这是非常冗长的,并且破坏了我们通过在 lambda 中使用(实际上是最终的)变量获得的漂亮语法。这在技术上是最优的吗?在良好的语法和保持 ref 比必要更长的可能性之间总是存在权衡吗?

【问题讨论】:

使用 C++ en.cppreference.com/w/cpp/language/lambda#Lambda_capture 我认为你的假设是不正确的。 lambda 不包含对 MyClass 的引用,仅包含对字符串的引用。 @PaulBoddington: That's not correct 我的立场是正确的。谢谢 Lukas 并向@Noel 道歉 @Ingo: this 被隐式引用 【参考方案1】:

首先将你的成员分配给一个局部变量:

class MyClass 
  final String foo = "foo";
  private Consumer<String> getFn() 
    String localFoo = foo;
    return bar -> System.out.println(bar + localFoo);
  

现在,lambda 只捕获 getFn() 内的局部变量。 MyClass.this 不再被捕获。

另一种选择,稍微详细一点,委托给辅助方法:

class MyClass 
  final String foo = "foo";
  private Consumer<String> getFn() 
    return getFn(foo);
  
  private static Consumer<String> getFn(String localFoo) 
    return bar -> System.out.println(bar + localFoo);
  

【讨论】:

@AR.3: foo 是 lambda 中的成员引用。每次调用 lambda 时都需要解决它。例如,如果在初始化 foo 之前从初始化程序调用了 lambda,则它可能是 null。由于一些反射魔法,它可以再次更改。 @srborlongan:除了可读性之外,我没有看到任何缺点?高阶 lambdas 还不是很常见(目前) @AR.3:当 lambda 表达式直接访问 MyClass.foo 时,它确实使用编译时常量,而不是实际读取字段。在 Java 中从未考虑过字段尚未初始化或反射魔法存在的可能性。但是,它仍然捕获this,可能要遵循某个规则,例如任何实例字段访问都要求捕获this,但是,我在Java语言规范中找不到该部分。 @Lukas Eder:当localFoo 被声明为final 时,foo 字段将永远不会在运行时被读取。相反,lambda 表达式将直接使用"foo",而不管在您调用getFn()foo 真正包含的内容(当然是第一个解决方案)。即使没有将localFoo声明为final,也会直接用"foo"初始化,而不是实际读取foo 我不判断它是否与这个特定问题相关(尽管值得注意的是,与可能不同的实际代码相比,所写问题的行为可能不同)。然而,AR3 在第一条评论中提出了这一点,并且有一条评论指出它需要更正。后来我才注意到 AR.3 对此提出了一个新的特定问题,并在那里添加了一个more complex answer...【参考方案2】:

Lukas Eder 的 local-final-variable 和 helper-method-delegation 解决方案的组合:

class MyClass 
  final String foo = "foo";
  private Consumer<String> getFn() 
    return apply(
      foo,
      localFoo -> bar -> System.out.println(bar + localFoo)
    );
  
  private static <IN, OUT> OUT apply(
    final IN in,
    final Function<IN, OUT> function
  ) 
    return function.apply(in);
  

【讨论】:

以上是关于Java Lambda 引用封闭对象:替换为私有静态类?的主要内容,如果未能解决你的问题,请参考以下文章

Java 8 Lambda表达式之方法引用 ::双冒号操作符

Java之线程池和Lambda表达式

Java8学习笔记 - 方法引用:Lambda的语法糖

Java8学习笔记 - 方法引用:Lambda的语法糖

Lambda+Stream替换集合中每个对象的指定字段值

如何用接口方法的方法引用替换lambda? [关闭]