Java8:λ表达式作为实参

来源:互联网 发布:asp连接sql数据库代码 编辑:程序博客网 时间:2024/04/27 21:58

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写出。

清单1package java8.passingCode;/** * 按照SICP 1.3.1的命名sum_integers求代数和,sum_cubes求立方数的代数和,pi求1/(1*3)+1/(5*7)+1/(9*11)+...的某些项的和 *  * @author (yqj2065)  * @version (0.1) */public class SomeSum{    public static double identity (int x){          return x;      }      public static double sum_integers(int a,int b){          int sum=0;          for(int i =a;i<=b;i++){              sum+=identity(i);          }          return sum;              }      public static double cube(int x){          return x*x*x;      }      public static int sum_cubes(int a,int b){          int sum=0;          for(int i =a;i<=b;i++){              sum+=cube(i);          }          return sum;              }      /**      * the sum of a sequence of terms in the series 1/(1*3)+1/(5*7)+1/(9*11)+...      */      public static double item(int x){          return 1.0/(x*(x+2));      }      public static double pi(int a,int b){          double sum=0;          for(int i =a;i<=b;i+=4){              sum+=item(i);          }          return sum;              }      public static void main(String[] a){          System.out.println(sum_cubes(1,10));          System.out.println(sum_integers(1,10));          System.out.println("pi="+8*pi(1,1000));      }  }
《Java 8 in action·Chapter 2. Passing code with behavior parameterization》给出了较为简单的例子,采用策略模式进行重构。

2.模板方法模式 Vs. 高阶函数

在Java8之前,可以采用模板方法模式重构清单1。

package java8.passingCode;/** * 模板类 */public abstract class Sum{    //template Method    public final double getSum(int a,int b){          double sum=0;          for(int i =a;i<=b; i= next(i)){              sum+=item(i);          }          return sum;              }    abstract public int next(int i);     abstract public double item(int x); }
而后,求代数和、求立方数的代数和,求pi需要分别编写模板类Sum的子类。

<pre code_snippet_id="1710335" snippet_file_name="blog_20160607_4_7574436" name="code" class="java">package java8.passingCode;
public class Sum_pi extends Sum{ @Override public double item(int x) { return 1.0 / (x * (x + 2)); } @Override public int next(int i) { return i + 4; }}
package java8.passingCode;
public class Test{ public static void main(String[] a) { System.out.println("pi=" + 8 * new Sum_pi().getSum(1, 1000)); }}

采用模板方法模式,使用纯粹的面向对象技术,实现了行为参数化

进一步,将abstract Sum中的两个抽象方法放入两个接口NextInterface和ItemInterface得到Sum_8,注意,getSum(int a,int b,NextInterface iNext,ItemInterface iItem)中形参类型有两个接口。

<pre code_snippet_id="1710335" snippet_file_name="blog_20160607_4_7574436" name="code" class="java">package java8.passingCode;
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; }}

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;@FunctionalInterfacepublic 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则非常合适。


0 0