lambda 表达式与静态方法

Posted

技术标签:

【中文标题】lambda 表达式与静态方法【英文标题】:lambda expression vs static method 【发布时间】:2017-10-30 01:27:32 【问题描述】:

我有一个关于没有代码重复的 lambda 表达式的可重用性的问题。例如,如果我有一个辅助方法,我可以轻松地将其编码为静态方法,并且可以从其他类中引用它而无需重复代码。这在 lambda 表达式中如何工作? 示例:我编写了以下静态方法

public class MyUtil 

    public static int doubleMe(int x) 
        return x * 2;
    

我可以在整个项目的多个地方重复使用相同的方法,而无需重复代码

public class A 

    public void someOtherCalculation() 
        MyUtil.doubleMe(5);
    


public class B 

    public void myCalculation() 
        MyUtil.doubleMe(3);
    

当涉及到 lambda 函数时,它是如何工作的,编写一次函数并在多个类中使用相同的函数。

Function<Integer, Integer> doubleFunction = x -> x * 2;

在我的示例中,我将在哪里编写上述 lambda 函数以及如何在 A 类和 B 类中重用相同的函数?

【问题讨论】:

“代码重用”在这里是一个错误的目标。需要代码表现力。使用不“重用”代码的 lambda 有什么可能的危害,你有什么证据表明它没有?当许多不同的函子可以在算法中替代时,Lambdas 是适用的。你会为所有这些都写一个显式的静态方法,并为每个方法创建一个方法引用,这样你就可以说,“看,妈妈,我正在使用 lambdas!”?在这种情况下按惯例使用 lambda 不是更有意义吗?您寻求的真正工程效益是什么? 如果要复用一个函数,那么定义一个实现Function接口的类。静态方法不是函数。 【参考方案1】:

上面的 lambda 函数在哪里写

由于您的函数没有引用任何字段,因此将其放在静态最终字段中是合适的:

class Utility 
    public static final Function<Integer,Integer> doubleFunction = x -> x * 2;

如何在 A 类和 B 类中重复使用相同的内容?

您可以将其称为Utility.doubleFunction,并在需要它的上下文中传递它:

callMethodWithLambda(Utility.doubleFunction);

请注意,方法引用可以让您定义一个函数,并像使用 lambda 一样使用它:

class Utility 
    public static Integer doubleFunction(Integer x) 
        return x*2;
    

...
callMethodWithLambda(Utility::doubleFunction);

这种方法非常灵活,因为它允许您在多个上下文中根据需要重用相同的代码。

【讨论】:

【参考方案2】:

确实,匿名函数适用于代码重用必要的情况。

愚蠢的例子,但假设您使用map 为列表中的每个数字添加两个。如果这是您可能到处都需要的常见操作,那么将数字加二的静态函数比在任何地方编写相同的 lambda 更有意义。

但是,如果您有一个将两个添加到列表中的函数,则将“添加两个”函数在本地定义为 lambda 会更有意义,这样您就不会使用其他任何地方都不需要的代码来插入您的类.

在编写大量使用高阶函数的 Clojure 时,我创建本地匿名函数来整理我正在编写的“完整”函数中的代码是很常见的。这些匿名函数中的绝大多数在“全局”范围(或类范围)中都是无意义的;特别是因为它们通常对局部变量有闭包,所以它们无论如何都不能是全局的。

【讨论】:

我一直假设这一点,但从未在任何地方阅读过。很有帮助。【参考方案3】:

使用 lambda 表达式,您无需担心可重用性(事实上,大多数 lambdas 根本没有被重用)。如果你想要一个 Function 指针指向这个方法,你可以像下面这样声明:

Function<Integer, Integer> doubleFunction = MyUtil::doubleMe;

并将其传递给任何方法或流以应用/映射,例如:

public static void consume(Function<Integer, Integer> consumer, int value)
    System.out.println(consumer.apply(value));


public static void main(String[] args) throws Exception
    Function<Integer, Integer> doubleFunction = MyUtil::doubleMe;
    consume(doubleFunction, 5);

【讨论】:

【参考方案4】:

与其他答案不同。我想用TDD的方式回答你的问题。

如果您的doubleMe 就像您写的那样简单,那就是您应该停止滥用方法表达式引用,直接将其作为通用方法调用来调用。

如果您的doubleMe 非常复杂,以至于您想测试doubleMe 独立,您需要通过依赖注入显式地制作隐式依赖,以测试它们是否可以通过它们的通信协议一起工作。但是java不能直接引用方法,除非您使用反射apiMethod/使用实现SAM接口的匿名类,该接口将请求委托给jdk-8之前的方法。令人高兴的是,您可以将方法表达式引用引用到 jdk-8 中的功能接口。所以你可以通过使用函数式接口使隐式依赖显式,然后我想写一些通信协议测试如下:

@Test
void applyingMultiplicationWhenCalculating???()
    IntUnaryOperator multiplication = mock(IntUnaryOperator.class);
    B it = new B(multiplication);

    it.myCalculation();

    verify(multiplication).applyAsInt(3);

AND 那么你的类如B 应用的依赖注入更像如下:

public class B 
    IntUnaryOperator multiplication;

    public B(IntUnaryOperator multiplication)
          this.multiplication = multiplication;
    

    public void myCalculation() 
          multiplication.applyAsInt(3);
    

那么您可以通过将方法表达式引用引用到功能接口来重用方法,如下所示:

A a = new A(MyUtil::doubleMe);
B b = new B(MyUtil::doubleMe);

【讨论】:

【参考方案5】:

您可以执行以下操作。

class Fn 
  public static final Function<Integer, Integer> X2TIMES = x -> x *2;


class Test 
  public static void main (String[] args) 
    System.out.println(Fn.X2TIMES.apply(5));
  

【讨论】:

以上是关于lambda 表达式与静态方法的主要内容,如果未能解决你的问题,请参考以下文章

Java8函数式接口/Lambda表达式/接口默认方法/接口静态方法/接口冲突方法重写/lambda表达式指定泛型类型等

Java8都有哪些新特性

Java8 之 lambda 表达式方法引用函数式接口默认方式静态方法

Java自学-Lambda 方法引用

方法引用(Method References)

方法引用(Method References)