从另一个角度谈谈Lambda
表达式和函数传递的思想。
假设有一个(数学,不是Java
)函数f
,比如说定义是
f(x) = x + 10
常见的一个问题就是,画在纸上之后函数下方的面积(把x轴作为基准)。如下图函数f(x) = x + 10
,x
从3
到7
下方的面积:
在这个例子里,函数f
是一条直线,因此很容易通过梯形方法(画几个三角形)来算出面积:
1/2 × ((3 + 10) + (7 + 10)) × (7 - 3) = 60
那么这在Java
里面如何表达呢?第一个问题是把积分号或dy/dx
之类的换成熟悉的编程语言符号。
确实,根据第一条原则需要一个方法,比如说叫integrate
,它接受三个参数:一个是f
,还有上下限(这里是3.0
和7.0
)。于是写在Java
里就是下面这个样子,函数f
是被传递进去的:integrate(f, 3, 7)
请注意,不能简单地写:
integrate(x + 10, 3, 7)
原因有二。第一,x
的作用域不清楚;第二,这将会把x +10
的值而不是函数E
传给积分。事实上,数学上dx
的秘密作用就是说“以x
为自变量、结果是x+10
的那个函数。”
前面说过,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
是一个线性函数(直线)。可能会写成类似数学的形式:
//错误的Java码!(函数的写法不能像数学里那样。)
public double integrate((double -> double)f, double a, double b) {
return (f(a) + f(b)) * (b - a) / 2.0;
}
但因为Lambda
表达式只能用于接受函数式接口的地方(这里就是Function
),所以必须得写成这个样子:
public double integrate(DoubleFunction<Double> f, double a, double b) {
return (f.apply(a) + f.apply(b)) * (b - a) / 2.0;
}
顺便提一句,有点儿可惜的是必须写f.apply(a)
,而不是像数学里面写f(a)
,但Java
无法摆脱“一切都是对象”的思想——它不能让函数完全独立。
以下是关键的概念。
Lambda
表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表。Lambda
表达式让你可以简洁地传递代码。Lambda
表达式。Lambda
表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。Java 8
自带一些常用的函数式接口,放在java.util.function
包里,包括Predicate
、Function
、Supplier
、Consumer
和Binaryoperator
。Predicate
和Function
等通用函数式接口的原始类型特化:IntPredicate
、IntToLongFunction
等。Lambda
提高灵活性和可重用性。Lambda
表达式所需要代表的类型称为目标类型。Comparator
、Predicate
和Function
等函数式接口都有几个可以用来结合Lambda
表达式的默认方法。