Java8 λ表达式的核心思想是Behavior parameterization(Java 8 in action)或SICP 1.3.1 Procedures as Arguments。本文说明Java8 λ表达式与Scheme的异同,并解释相关的概念。
1.靶子
SICP 1.3.1 Procedures as Arguments,给出的例子:“若干个函数拥有相似的算法或代码结构,请对此加以抽象”:
Scheme代码为:
(define (sum-integers a b)
(if (> a b)
0
(+ a (sum-integers (+ a 1) b))))
(define (pi-sum a b)
(if (> a b)
0
(+ (/ 1.0 (* a (+ a 2))) (pi-sum (+ a 4) b))))
Java代码整合为4.3.3 累积函数
Scheme对上述函数加以抽象或一般化的技术为高阶函数。高阶函数(的一种形式是函数作为形参,高阶函数的另一种形式是返回一个函数)。高阶函数sum中包含了两个函数形参term和next。
(define (sum term a next b)
(if (> a b)
0
(+ (term a)
(sum term (next a) next b))))
小结:
- 行为参数化,在Java 8之前,通过多态/策略模式/模板方法模式已经可以实现。
- Java中getSum(NextInterface iNext)这种方法,如果形参类型为函数接口,是否适合称之为高阶函数呢?理论上看,在定义方法时,Java不存在高阶函数概念;而在方法使用者看来,System.out.println("sum_cube(1,10)="+ new Sum_8().getSum(1, 10,i->++i,x->x*x*x) ); 可以视为高阶函数。
3.人妖
Java8:λ表达式是匿名类的语法糖中说明:Java的λ表达式是一个(拥有省略了名字的函数的)某个函数接口的匿名(实现)类。(注意:这里是从Java语言层面给出的论断,正如Java的int类型为32位一样,不考虑JVM的具体实现)。但是,getSum的实参则是Java的λ表达式,使用起来的感觉,就是在传递函数!
Java的λ表达式不是函数式编程语言如Scheme中的λ表达式。
Scheme中的λ表达式是函数,Java的λ表达式是函数接口的实现类。
换言之,Java8 的目的,是让程序员认为Java的λ表达式是函数,如同让程序员认为人妖是女人。getSum的实参是Java的λ表达式,使用起来的感觉,就是在传递函数!
对于Java的λ表达式,我们与其认为它类似函数式语言的λ表达式,不如说它类似C语言的函数指针。
为了让程序员认为人妖是女人,Java的λ表达式需要配套的概念:函数接口、method references等。
例如函数接口:
package java8.passingCode;
@FunctionalInterface
public interface ItemInterface{
public double item(int x);
}
传递函数!
public class Test{
public static void main(String[] a) {
System.out.println("pi=" + 8 * new Sum_pi().getSum(1, 1000));
System.out.println("sum_integers(1,10)=" + new Sum() {
@Override public double item(int x) {
return x;
}
@Override
public int next(int i) {
return ++i;
}
}.getSum(1, 10));
System.out.println("sum_cube(1,10)="+ new Sum_8().getSum(1, 10,i->++i,x->x*x*x) );
}
}
Test类中,出现了多种传递函数的方式:Sum_pi是独立的类,求代数和使用了Sum的匿名类;而求立方数的代数和使用了函数接口的实现——λ表达式,
实参是Java的λ表达式。
4.进一步抽象
Sum_8依赖于NextInterface和ItemInterface两个函数接口,可以借用java.util.function.*预定义的函数接口。package java8.passingCode;
import java.util.function.*;
public class Sum_8 {
/*public final double getSum(int a,int b,NextInterface iNext,ItemInterface iItem){
double sum=0;
for(int i =a;i<=b; i=iNext.next(i)){
sum+=iItem.item(i);
}
return sum;
}*/
public final double getSum(int a,int b,IntUnaryOperator iuo,Function<Double,Double> f){
double sum=0;
for(int i =a;i<=b; i=iuo.applyAsInt(i)){
sum+=f.apply(1.0*i);
}
return sum;
}
}
在减少了两个函数接口的同时,applyAsInt(i)显然不如next(i)含义清晰。java.util.function.*预定义的函数接口,反映了一个事实:Java的λ表达式真的是人妖。
《Java 8 in action·Chapter 2》的例子,使用List<T>、Predicate<T>代替List<Apple>、ApplePredicate则非常合适。