在流操作中使用方法引用和函数对象之间的区别?

K E*_*son 2 java lambda java-8 method-reference

当使用Java 8流时,我经常发现我需要重构一个多语句lambda表达式.我将用一个简单的例子来说明这一点.假设我已经开始编写这段代码:

Stream.of(1, 3).map(i -> {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
}).forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

现在我不太喜欢map通话中的大型lambda表达式.因此,我想把它重构出来.我看到两个选项,要么我Function在我的班级中创建一个实例:

private static Function<Integer, String> mapper = i -> {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
};
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

Stream.of(1, 3).map(mapper).forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

或者我只是制作一个方法:

private static String map(Integer i) {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
}
Run Code Online (Sandbox Code Playgroud)

并使用方法参考:

Stream.of(1, 3).map(Test::map).forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

除了明显的味道问题外,这两种方法都有任何优点或缺点吗?

例如,我知道堆栈跟踪在方法参考案例中变得更具可读性,这是一个小优势.

Mar*_*nik 5

除非有一些我不知道的额外魔法,否则当前的lambda实现会将你的非捕获lambda变为静态方法,并将缓存lambda实例.通过明确地执行相同的操作(static final对lambda 的引用),您基本上复制了隐式工作,因此您最终会得到两个对同一事物的缓存引用.你也在打败lambda实例的延迟初始化,否则你将获得免费的.

这就是为什么我更喜欢方法参考:它更简单,更惯用,而且在实现方面似乎也更轻巧.

  • 我想补充一点,即使你不调用包含lambda的特定方法,也会初始化`static final`.这实际上意味着`LambdaMetafactory`将生成额外的匿名类,即使您从未使用它.使用方法引用或就地lambda只有在实际需要时才会生成此类. (3认同)
  • 好.顺便说一句,bootstrapping*可以*同时运行.它只能保证最多只能使用一个结果,因此在并发生成的情况下,所有其他结果都会被删除.但是afaik,在Oracle的实现中,工厂本身将明确锁定以避免在并发情况下两次工作. (2认同)
  • 结果是一个`CallSite`实例,其中包含一个`MethodHandle`.在无状态lambda的情况下,它是一个[常量句柄](http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.html#constant-java.lang.Class -java.lang.Object-)持有一个构造的lambda实例.否则,它将是一个指向lambda实现类的构造函数的句柄(或该类的`static`工厂方法,我不知道为什么他们决定这样做).无论哪种方式,`invokedynamic`指令的后续执行就像执行`MethodHandle`一样. (2认同)

Stu*_*rks 5

将多行lambda表达式重构为普通方法并在流中使用方法引用的一些原因是可维护性和可测试性.

当非平凡映射器函数变为普通方法时,它获取一个名称,它可用于单元测试框架.您可以轻松编写直接调用它的测试,并在必要时将其删除.

该方法还可以包含与之关联的文档注释,包括参数和返回值的文档.

如果映射器函数很复杂,这两个都非常有用.

将lambda表达式赋给字段并没有错,但我认为这样做的主要原因是如果字段在运行时被修改,例如,当应用程序的状态发生变化时.即使在这些情况下,我也可以考虑编写普通方法,然后使用方法引用而不是多行lambda表达式来分配字段.

使用字段的一个缺点是它要求您明确声明功能接口的泛型类型参数.IDE可以帮助解决这个问题,但它会增加程序的混乱.

我猜想使用方法引用的普通方法总是优于使用用lambda表达式初始化的final字段.