2年前 (2022-01-10)  Java系列 |   抢沙发  368 
文章评分 0 次,平均分 0.0

用lambda表达式进行Java编程

在JavaOne 2013的技术主题演讲中,Oracle Java平台组首席架构师Mark Reinhold将lambda表达式描述为Java编程模型有史以来最大的一次升级。虽然lambda表达式有很多应用程序,但本文主要关注数学应用程序中经常出现的一个特定示例;也就是说,需要将函数传递给算法。

数学中的许多应用要求函数作为参数传递给算法。大学代数和基础微积分的例子包括解方程或计算函数的积分。15年来,Java一直是我在大多数应用程序中选择的编程语言,但它是我经常使用的第一种语言,它不允许我以简单、直接的方式将函数(从技术上讲是指向函数的指针或引用)作为参数传递。随着即将发布的Java8,这个缺点即将改变。

lambda表达式的功能远远超出了单个用例,但是研究同一示例的各种实现应该让您对lambda将如何为Java程序带来好处有一个明确的认识。在本文中,我将使用一个常见的例子来帮助描述问题,然后提供在C++中使用java语言编写的解决方案,在lambda表达式之前使用java,以及用lambda表达式来编写Java。请注意,理解和理解本文的要点并不需要有很强的数学背景。

数学示例中的Lambda表达式

本文中使用的示例是基本微积分中的辛普森规则。辛普森规则,或者更具体地说是复合辛普森规则,是一种近似定积分的数值积分技术。如果你不熟悉定积分的概念,不要担心;您真正需要了解的是,辛普森规则是一种基于四个参数计算实数的算法:

  • 我们想要集成的函数。
  • 两个实数a和b,表示实数线上区间[a,b]的端点。(请注意,上述功能在此时间间隔内应是连续的。)
  • 指定子间隔数的偶数整数n。在执行辛普森规则时,我们将区间[a,b]划分为n个子区间。

为了简化演示,让我们关注编程接口,而不是实现细节。(老实说,我希望这种方法能让我们绕过关于实施辛普森规则的最佳或最有效方法的争论,这不是本文的重点。)我们将对参数a和b使用类型double,对参数n使用类型int。要集成的函数将接受类型double的单个参数,并返回类型double的值。

C++中的函数参数

为了提供比较的基础,让我们从C++规范开始。当在C++中传递函数作为参数时,我通常倾向于使用TyPulf指定函数参数的签名。清单1显示了一个名为辛普森的C++头文件。h,它为函数参数和用于C++函数的集成编程接口指定了TyPulf。集成的函数体包含在一个名为辛普森的C++源代码文件中。cpp(未显示)并提供辛普森规则的实现。

清单1。辛普森规则的C++头文件

#if !defined(SIMPSON_H)
#define SIMPSON_H
#include <stdexcept>
using namespace std;
typedef double DoubleFunction(double x);
double integrate(DoubleFunction f, double a, double b, int n)
    throw(invalid_argument);
#endif

调用集成在C++中是很简单的。作为一个简单的例子,假设您想要使用辛普森规则,使用30个子区间来近似正弦函数从0到π(PI)的积分。(任何完成微积分的人,我都应该能够在没有计算器帮助的情况下准确地计算答案,这是集成函数的一个很好的测试用例。)假设包含了适当的头文件,如<cmath>和“simpson.h”,则可以调用函数integrate,如清单2所示。

double result = integrate(sin, 0, M_PI, 30);

就这些。C++中,传递正弦函数时,传递其他三个参数的过程很简单。

没有lambda表达式的Java

现在,让我们看看如何在Java中指定Simpson规则。不管我们是否使用lambda表达式,我们使用清单3中所示的java接口代替C++ Type,来指定函数参数的签名。

清单3。函数参数的Java接口

public interface DoubleFunction
  {
    public double f(double x);
  }

为了在java中实现辛普森规则,我们创建了一个名为辛普森的类,该类包含一个方法,与C++中的四个参数类似。与许多自包含的数学方法一样(例如,请参见java.lang.Math),我们将使integrate成为一个静态方法。方法集成规定如下:

清单4。Simpson类中方法集成的Java签名

public static double integrate(DoubleFunction df, double a, double b, int n)

到目前为止,我们在Java中所做的一切都与是否使用lambda表达式无关。lambda表达式的主要区别在于在调用方法integrate时如何传递参数(更具体地说,如何传递函数参数)。首先,我将说明在版本8之前的Java版本中如何实现这一点;i、 没有lambda表达式。与C++例子一样,假设我们要用30个子区间来近似正弦函数的积分,从0席到π(π)。

使用正弦函数的适配器模式

在Java中,我们有一个在Java中可用的正弦函数的实现。但是在Java8之前的Java版本中,没有简单、直接的方法将这个正弦函数传递给Simpson类中集成的方法。一种方法是使用适配器模式。在本例中,我们将编写一个简单的适配器类来实现DoubleFunction接口,并对其进行调整以调用sine函数,如清单5所示。

import com.softmoore.math.DoubleFunction;
public class DoubleFunctionSineAdapter implements DoubleFunction
  {
    public double f(double x)
      {
        return Math.sin(x);
      }
  }

使用这个适配器类,我们现在可以调用Simpson类的integrate方法,如清单6所示。

DoubleFunctionSineAdapter sine = new DoubleFunctionSineAdapter();
double result = Simpson.integrate(sine, 0, Math.PI, 30);

让我们停下来,比较一下调用C++中集成调用所需的内容和java早期版本所需的内容。用C++,我们简单地调用积分,传递四个参数。使用Java,我们必须创建一个新的适配器类,然后实例化这个类以进行调用。如果我们想集成几个函数,我们需要为每个函数编写一个适配器类。

通过在调用integrate中创建适配器类的新实例,我们可以将调用integrate所需的代码从两个Java语句略微缩短为一个。使用匿名类而不是创建单独的适配器类是稍微减少总体工作量的另一种方法,如清单7所示。

清单7。使用匿名类调用方法Simpson.integrate

DoubleFunction sineAdapter = new DoubleFunction()
  {
    public double f(double x)
      {
        return Math.sin(x);
      }
  };
double result = Simpson.integrate(sineAdapter, 0, Math.PI, 30);

如果没有lambda表达式,清单7中所看到的代码量最少,您可以用java编写调用集成方法,但它仍然比C++所需要的要繁琐得多。我也不太喜欢使用匿名类,尽管我在过去经常使用它们。我不喜欢这种语法,并且一直认为它是Java语言中一种笨拙但必要的技巧。

带有lambda表达式和函数接口的Java

现在让我们看看如何在Java8中使用lambda表达式来简化在Java中集成的调用。因为接口DoubleFunction只需要实现一个方法,所以它是lambda表达式的候选。如果我们事先知道要使用lambda表达式,我们可以用@functionanterface注释接口,这是一个新的Java8注释,表示我们有一个函数接口。请注意,此注释不是必需的,但它为我们提供了一个额外的检查,以确保所有内容都是一致的,类似于Java早期版本中的@Override注释。

lambda表达式的语法是括在括号中的参数列表、箭头标记(->)和函数体。正文可以是语句块(用大括号括起来)或单个表达式。清单8显示了一个lambda表达式,它实现了接口DoubleFunction,然后传递给方法integrate

DoubleFunction sine = (double x) -> Math.sin(x);
double result = Simpson.integrate(sine, 0, Math.PI, 30);

注意,我们不必编写适配器类或创建匿名类的实例。还请注意,我们可以通过替换lambda表达式本身(double x) -> Math.sin(x)上述内容写入一条语句中,对于上面第二条语句中的参数sine,删除第一条语句。现在我们更接近C++中的简单语法了。但是等等!还有更多!

函数接口的名称不是lambda表达式的一部分,但可以根据上下文进行推断。lambda表达式参数的类型double也可以从上下文中推断出来。最后,如果lambda表达式中只有一个参数,则可以省略括号。因此,我们可以将调用方法integrate的代码缩写为一行代码,如清单9所示。

double result = Simpson.integrate(x -> Math.sin(x), 0, Math.PI, 30);

但是等等!还有更多!

Java8中的方法引用

Java8中的另一个相关特性是方法引用,它允许我们按名称引用现有方法。方法引用可以用来代替lambda表达式,只要它们满足函数接口的要求。如参考资料中所述,有几种不同的方法引用,每种方法引用的语法略有不同。对于静态方法,语法是Classname::methodName。因此,使用方法引用,我们可以调用C++中的java集成方法。将下面列出的java 8调用与上面C++清单2所示的原始C++调用进行比较。

double result = Simpson.integrate(Math::sin, 0, Math.PI, 30);

最后,虽然我更喜欢编写接口DoubleFunction,因为我认为它使代码更容易理解,但即使是该接口也可以消除。Java8有一个新的包,java.util.function,它包含许多常用的函数接口。许多是使用泛型表示的,但也有对基元类型的专门化。我将使用这些接口中的一个来代替DoubleFunction,这是众所周知的读者练习。(有关提示,请仔细查看java.util.function中的DoubleUnaryOperator

总结

总的来说,我发现用Java编写代码是一件很愉快的事情,15年来它一直是我首选的编程语言。然而,在(仍然)一些地方,Java的语法看起来很笨拙。通过添加lambda表达式,Java8将更正其中一个表达式。

原文地址:https://www.infoworld.com/article/2092260/java-programming-with-lambda-expressions.html

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2542.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册