3.9 数学中的类似思想
如果你上学的时候对数学很擅长,那这一节就从另一个角度来谈谈Lambda表达式和函数传递的思想。你可以跳过它,书中没有任何其他内容依赖这一节,不过从另一个角度看看也挺好的。
3.9.1 积分
假设你有一个(数学,不是Java)函数
,比如说定义是

那么,(工科学校里)经常问的一个问题就是,求画在纸上之后函数下方的面积(把
轴作为基准)。比如对于图3-8所示的面积你会写
或 

图 3-8 函数
(
从3到7)下方的面积
在这个例子里,函数
是一条直线,因此你很容易通过梯形方法(画几个三角形和矩形)来算出面积:
1/2 × ((3 + 10) + (7 + 10)) × (7 – 3) = 60
那么这在Java里面如何表达呢?你的第一个问题是把积分号或
之类的换成熟悉的编程语言符号。
确实,根据第一条原则你需要一个方法,比如说叫integrate,它接受三个参数:一个是f,还有上下限(这里是3.0和7.0)。于是写在Java里就是下面这个样子,函数f是作为参数被传递进去的:
integrate(f, 3, 7)
请注意,你不能简单地写:
integrate(x + 10, 3, 7)
原因有两个。第一,x的作用域不清楚;第二,这将把x + 10的值而不是函数f传给积分。
事实上,数学上
的秘密作用就是说“以
为自变量、结果是
的那个函数。”
3.9.2 与Java 8的Lambda联系起来
前面说过,Java 8的表示法(double x) -> x + 10(一个Lambda表达式)恰恰就是为此设计的,因此你可以写:
integrate((double x) -> x + 10, 3, 7)
或者
integrate((double x) -> f(x), 3, 7)
或者,用前面说的方法引用,只要写:
integrate(C::f, 3, 7)
这里C是包含静态方法f的一个类。理念就是把f背后的代码传给integrate方法。
现在你可能在想如何写integrate本身了。我们还假设f是一个线性函数(直线)。你可能会写成类似数学的形式:
public double integrate((double -> double) f, double a, double b) { ←---- 错误的Java代码!(函数的写法不能像数学里那样。)return (f(a) + f(b)) * (b - a) / 2.0}
不过,由于Lambda表达式只能用于接受函数式接口的地方(这里就是DoubleFunction4),所以你必须得写成这个样子:
4使用DoubleFunction比Function更高效,因为它避免了结果的装箱操作。
public double integrate(DoubleFunction<Double> f, double a, double b) {return (f.apply(a) + f.apply(b)) * (b - a) / 2.0;}
或者用DoubleUnaryOperator,这样也可以避免对结果进行装箱:
public double integrate(DoubleUnaryOperator f, double a, double b) {return (f.applyAsDouble(a) + f.applyAsDouble(b)) * (b - a) / 2.0;}
顺便提一句,有点可惜的是你必须写f.apply(a),而不是像数学里面写f(a),但Java无法摆脱“一切都是对象”的思想——它不能让函数完全独立!
