精华内容
下载资源
问答
  • 匿名函数 匿名函数是闭包的核心,匿名...与普通的面向对象编程方式不同,匿名函数的代码是直接写在调用处的,不需要额外写一个类,编写方法的代码。这样的好处就是更直接。下面的示例是设置一个定时器,每2秒输出he

    今天写一篇文章来专门介绍一下PHP的闭包。从5.3版本开始PHP就增加了匿名函数支持,经过数个版本迭代到现在的PHP5.6、PHP7,PHP语言的闭包已经非常完善了。再结合Swoole提供的事件驱动支持,PHP的闭包功能非常强大而且很优雅。

    匿名函数

    匿名函数是闭包的核心,匿名函数在PHP里实际上是一个Closure类的对象(请注意是对象)。与普通的面向对象编程方式不同,匿名函数的代码是直接写在调用处的,不需要额外写一个类,编写方法的代码。这样的好处就是更直接。下面的示例是设置一个定时器,每2秒输出hello world。

    传统写法

    function timer () {
        echo "hello world";
    }
    Swoole\Timer::tick(2000, 'timer');
    

    闭包写法

    Swoole\Timer::tick(2000, function () {
        echo "hello world";
    });
    

    非闭包的传统写法,先要声明一个函数,再转入函数名称字符串。两段代码是分离的,不够直观。而闭包的写法把定时器的声明和定时器要执行的代码写在了一起,逻辑非常清晰直观。使用闭包语法可以很方便编写回调函数。在事件驱动编程、排序、array_walk等需要用户传入一段执行代码的场景中,闭包的写法非常优雅。

    闭包更强大的地方在于它可以直接在调用处引入外部变量。PHP中实现的方法就是use关键词。

    Use语法

    如果刚才的定时器需要传入一个变量,传统的写法只能通过全局变量来实现。与JS不同,PHP的变量引入是显式的,如果要引用外部变量必须使用use来声明。而JS是隐式的,匿名函数内部可以随意操作外部变量,无需声明。这样好处是少写了一点代码,缺点是存在风险和混乱。

    传统写法

    $str = "hello world";
    function timer () {
        global $str;
        echo $str;
    }
    Swoole\Timer::tick(2000, 'timer');
    

    闭包写法

    $str = "hello world";
    Swoole\Timer::tick(2000, function () use ($str) {
        echo $str;
    });
    

    闭包写法使用use直接引入了当前的$str变量,而不需要使用global全局变量。另外如果是在swoole的事件驱动编程模式,使用global就无法实现异步并发了,因为global全局变量只有1个,如果同时有多个客户端请求,每个请求要查询数据库,输出不同的内容,传统的编程方法就不太容易实现,需要使用全局变量数组,以客户端的ID为KEY保存各自的数据。

    传统写法

    $requestArray = array();
    $dbResultArray = array();
    
    function my_request($request, $response) {
        global $dbResultArray, $requestArray;
        $queryId = $db->query($sql, 'get_result');
        $requestArray[$request->fd] = array($request, $response);
        $dbResultArray[$queryId] = $request->fd;
    }
    
    function get_result($queryId, $queryResult) {
        global $dbResultArray, $requestArray;
        list($request, $response) = $requestArray[$dbResultArray[$queryId]];
        $response->end($queryResult);
    }
    
    $server->on('request', 'my_request');
    

    闭包写法

    $server->on('request', function ($request, $response) {
        $queryId = $db->query($sql, function ($queryId, $queryResult) use ($request, $response) {
            $response->end($queryResult);
        });
    });
    

    传统的写法非常复杂,需要反复多次从全局数组保存/提取数据。而闭包的写法非常简洁优雅,只用了几行代码就实现了同样的功能。闭包写法非常适合用来编写异步非阻塞回调模式的服务器程序。目前热门的编程语言中只有PHP和JS具备这种能力。

    闭包更多特性

    在类的方法中使用匿名函数,5.4以上的版本无需使用use引入this使this,直接可以在匿名函数中使用this来调用当前对象的方法。在swoole编程中,可以利用此特性减少$serv对象的use引入传递。

    class Server extends Swoole\Server {
        function onReceive($serv, $fd, $reactorId, $data) {
            $db->query($sql, function ($queryId, $queryResult) use ($fd) {
                $this->send($fd, $queryResult);
            }
        }
    }
    

    另外如果希望在闭包函数中修改外部变量,可以在use时为变量增加&引用符号即可。注意对象类型不需要加&,因为在PHP中对象默认就是传引用而非传值。

    更多学习内容可以访问【对标大厂】精品PHP架构师教程目录大全,只要你能看完保证薪资上升一个台阶(持续更新)

    以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的可以加入我的PHP技术交流群953224940

    进阶PHP月薪30k>>>架构师成长路线【视频、面试文档免费获取】

    在这里插入图片描述

    展开全文
  • 原因是平时编写代码时可能用到场景不多,用得最多是在有事件监听情况下,并且即使用到也很少去总结内部类用法。今天我们就来一探究竟。下面是本文目录大纲:  一.内部类基础  二.深入理解内部类  三...

    转自:微信公众号:java团长 

    说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉。原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法。今天我们就来一探究竟。下面是本文的目录大纲:

      一.内部类基础

      二.深入理解内部类

      三.内部类的使用场景和好处

      四.常见的与内部类相关的笔试面试题

      若有不正之处,请多谅解并欢迎批评指正。

     一.内部类基础

      在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。

      1.成员内部类

      成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

    class Circle {

        double radius = 0;

         

        public Circle(double radius) {

            this.radius = radius;

        }

         

        class Draw {     //内部类

            public void drawSahpe() {

                System.out.println("drawshape");

            }

        }

    }

      这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

    class Circle {

        private double radius = 0;

        public static int count =1;

        public Circle(double radius) {

            this.radius = radius;

        }

         

        class Draw {     //内部类

            public void drawSahpe() {

                System.out.println(radius);  //外部类的private成员

                System.out.println(count);   //外部类的静态成员

            }

        }

    }

      不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

    外部类.this.成员变量
    外部类.this.成员方法

      虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:

    class Circle {

        private double radius = 0;

     

        public Circle(double radius) {

            this.radius = radius;

            getDrawInstance().drawSahpe();   //必须先创建成员内部类的对象,再进行访问

        }

         

        private Draw getDrawInstance() {

            return new Draw();

        }

         

        class Draw {     //内部类

            public void drawSahpe() {

                System.out.println(radius);  //外部类的private成员

            }

        }

    }

      成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:

    public class Test {

        public static void main(String[] args)  {

            //第一种方式:

            Outter outter = new Outter();

            Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建

             

            //第二种方式:

            Outter.Inner inner1 = outter.getInnerInstance();

        }

    }

     

    class Outter {

        private Inner inner = null;

        public Outter() {

             

        }

         

        public Inner getInnerInstance() {

            if(inner == null)

                inner = new Inner();

            return inner;

        }

          

        class Inner {

            public Inner() {

                 

            }

        }

    }

      内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。

      2.局部内部类

      局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

    class People{

        public People() {

             

        }

    }

     

    class Man{

        public Man(){

             

        }

         

        public People getWoman(){

            class Woman extends People{   //局部内部类

                int age =0;

            }

            return new Woman();

        }

    }

      注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

      3.匿名内部类

      匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。下面这段代码是一段Android事件监听代码:

    scan_bt.setOnClickListener(new OnClickListener() {

                 

                @Override

                public void onClick(View v) {

                    // TODO Auto-generated method stub

                     

                }

            });

             

            history_bt.setOnClickListener(new OnClickListener() {

                 

                @Override

                public void onClick(View v) {

                    // TODO Auto-generated method stub

                     

                }

            });

      这段代码为两个按钮设置监听器,这里面就使用了匿名内部类。这段代码中的:

    new OnClickListener() {

                 

                @Override

                public void onClick(View v) {

                    // TODO Auto-generated method stub

                     

                }

            }

      就是匿名内部类的使用。代码中需要给按钮设置监听器对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。当然像下面这种写法也是可以的,跟上面使用匿名内部类达到效果相同。

    private void setListener()

    {

        scan_bt.setOnClickListener(new Listener1());       

        history_bt.setOnClickListener(new Listener2());

    }

     

    class Listener1 implements View.OnClickListener{

        @Override

        public void onClick(View v) {

        // TODO Auto-generated method stub

                 

        }

    }

     

    class Listener2 implements View.OnClickListener{

        @Override

        public void onClick(View v) {

        // TODO Auto-generated method stub

                 

        }

    }

      这种写法虽然能达到一样的效果,但是既冗长又难以维护,所以一般使用匿名内部类的方法来编写事件监听代码。同样的,匿名内部类也是不能有访问修饰符和static修饰符的。

      匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

      4.静态内部类

      静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

    public class Test {

        public static void main(String[] args)  {

            Outter.Inner inner = new Outter.Inner();

        }

    }

     

    class Outter {

        public Outter() {

             

        }

         

        static class Inner {

            public Inner() {

                 

            }

        }

    }

     二.深入理解内部类

      1.为什么成员内部类可以无条件访问外部类的成员?

      在此之前,我们已经讨论过了成员内部类可以无条件访问外部类的成员,那具体究竟是如何实现的呢?下面通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,下面是Outter.java的代码:

    public class Outter {

        private Inner inner = null;

        public Outter() {

             

        }

         

        public Inner getInnerInstance() {

            if(inner == null)

                inner = new Inner();

            return inner;

        }

          

        protected class Inner {

            public Inner() {

                 

            }

        }

    }

      编译之后,出现了两个字节码文件:

      反编译Outter$Inner.class文件得到下面信息:

    E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner

    Compiled from "Outter.java"

    public class com.cxh.test2.Outter$Inner extends java.lang.Object

      SourceFile: "Outter.java"

      InnerClass:

       #24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes

    t2/Outter

      minor version: 0

      major version: 50

      Constant pool:

    const #1 = class        #2;     //  com/cxh/test2/Outter$Inner

    const #2 = Asciz        com/cxh/test2/Outter$Inner;

    const #3 = class        #4;     //  java/lang/Object

    const #4 = Asciz        java/lang/Object;

    const #5 = Asciz        this$0;

    const #6 = Asciz        Lcom/cxh/test2/Outter;;

    const #7 = Asciz        <init>;

    const #8 = Asciz        (Lcom/cxh/test2/Outter;)V;

    const #9 = Asciz        Code;

    const #10 = Field       #1.#11; //  com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t

    est2/Outter;

    const #11 = NameAndType #5:#6;//  this$0:Lcom/cxh/test2/Outter;

    const #12 = Method      #3.#13; //  java/lang/Object."<init>":()V

    const #13 = NameAndType #7:#14;//  "<init>":()V

    const #14 = Asciz       ()V;

    const #15 = Asciz       LineNumberTable;

    const #16 = Asciz       LocalVariableTable;

    const #17 = Asciz       this;

    const #18 = Asciz       Lcom/cxh/test2/Outter$Inner;;

    const #19 = Asciz       SourceFile;

    const #20 = Asciz       Outter.java;

    const #21 = Asciz       InnerClasses;

    const #22 = class       #23;    //  com/cxh/test2/Outter

    const #23 = Asciz       com/cxh/test2/Outter;

    const #24 = Asciz       Inner;

     

    {

    final com.cxh.test2.Outter this$0;

     

    public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);

      Code:

       Stack=2, Locals=2, Args_size=2

       0:   aload_0

       1:   aload_1

       2:   putfield        #10; //Field this$0:Lcom/cxh/test2/Outter;

       5:   aload_0

       6:   invokespecial   #12; //Method java/lang/Object."<init>":()V

       9:   return

      LineNumberTable:

       line 16: 0

       line 18: 9

     

      LocalVariableTable:

       Start  Length  Slot  Name   Signature

       0      10      0    this       Lcom/cxh/test2/Outter$Inner;

     

     

    }

      第11行到35行是常量池的内容,下面逐一第38行的内容:

    final com.cxh.test2.Outter this$0;

      这行是一个指向外部类对象的指针,看到这里想必大家豁然开朗了。也就是说编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?下面接着看内部类的构造器:

    public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);

      从这里可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。

      2.为什么局部内部类和匿名内部类只能访问局部final变量?

      想必这个问题也曾经困扰过很多人,在讨论这个问题之前,先看下面这段代码:

    public class Test {

        public static void main(String[] args)  {

             

        }

         

        public void test(final int b) {

            final int a = 10;

            new Thread(){

                public void run() {

                    System.out.println(a);

                    System.out.println(b);

                };

            }.start();

        }

    }

      这段代码会被编译成两个class文件:Test.class和Test1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outter1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class(x为正整数)。

      

      根据上图可知,test方法中的匿名内部类的名字被起为 Test$1。

      上段代码中,如果把变量a和b前面的任一个final去掉,这段代码都编译不过。我们先考虑这样一个问题:

      当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制  的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容:

      我们看到在run方法中有一条指令:

    bipush 10

      这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

      下面再看一个例子:

    public class Test {

        public static void main(String[] args)  {

             

        }

         

        public void test(final int a) {

            new Thread(){

                public void run() {

                    System.out.println(a);

                };

            }.start();

        }

    }

      反编译得到:

      我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参a以参数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

      也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

      从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

      对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

      到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。

      3.静态内部类有特殊的地方吗?

      从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个读者可以自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。

     三.内部类的使用场景和好处

      为什么在Java中需要内部类?总结一下主要有以下四点:

      1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,

      2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。

      3.方便编写事件驱动程序

      4.方便编写线程代码

      个人觉得第一点是最重要的原因之一,内部类的存在使得Java的多继承机制变得更加完善。

     四.常见的与内部类相关的笔试面试题

      1.根据注释填写(1),(2),(3)处的代码

    public class Test{

        public static void main(String[] args){

               // 初始化Bean1

               (1)

               bean1.I++;

               // 初始化Bean2

               (2)

               bean2.J++;

               //初始化Bean3

               (3)

               bean3.k++;

        }

        class Bean1{

               public int I = 0;

        }

     

        static class Bean2{

               public int J = 0;

        }

    }

     

    class Bean{

        class Bean3{

               public int k = 0;

        }

    }

      从前面可知,对于成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外部类的实例化对象即可产生内部类的实例化对象。

      创建静态内部类对象的一般形式为:  外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()

      创建成员内部类对象的一般形式为:  外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()

      因此,(1),(2),(3)处的代码分别为:

    Test test = new Test();    
    
      Test.Bean1 bean1 = test.new Bean1();   

     

    Test.Bean2 b2 = new Test.Bean2();    

     

    Bean bean = new Bean();     
    
    Bean.Bean3 bean3 =  bean.new Bean3();   

      2.下面这段代码的输出结果是什么?

    public class Test {

        public static void main(String[] args)  {

            Outter outter = new Outter();

            outter.new Inner().print();

        }

    }

     

     

    class Outter

    {

        private int a = 1;

        class Inner {

            private int a = 2;

            public void print() {

                int a = 3;

                System.out.println("局部变量:" + a);

                System.out.println("内部类变量:" + this.a);

                System.out.println("外部类变量:" + Outter.this.a);

            }

        }

    }

     

    3
    2
    1

      最后补充一点知识:关于成员内部类的继承问题。一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:

      1)成员内部类的引用方式必须为 Outter.Inner.

      2)构造器中必须有指向外部类对象的引用,并通过这个引用调用super()。这段代码摘自《Java编程思想》

    class WithInner {

        class Inner{

             

        }

    }

    class InheritInner extends WithInner.Inner {

          

        // InheritInner() 是不能通过编译的,一定要加上形参

        InheritInner(WithInner wi) {

            wi.super(); //必须有这句调用

        }

      

        public static void main(String[] args) {

            WithInner wi = new WithInner();

            InheritInner obj = new InheritInner(wi);

        }

    }

    展开全文
  • 如果您从事大型企业项目开发,您就会熟悉编写模块化代码的好处。良构的、模块化的代码更容易编写、调试、理解和重用。Java 开发人员的问题是,函数编程范型长期以来只是通过像 Haskell、Scheme、Erlang 和 Lisp 这样...

    Java 语言中的函数编程

    利用闭包和高阶函数编写模块化的 Java 代码

    如果您从事大型企业项目开发,您就会熟悉编写模块化代码的好处。良构的、模块化的代码更容易编写、调试、理解和重用。Java 开发人员的问题是,函数编程范型长期以来只是通过像 Haskell、Scheme、Erlang 和 Lisp 这样的特殊语言实现的。在本文中,作者 Abhijit Belapurkar 展示了,如何使用像闭包(closure)和 高阶函数(higher order function)这样的函数编程结构,在 Java 语言中编写良构的、模块化的代码。

    Abhijit Belapurkar(abhijit_belapurkar@infosys.com), 高级技术架构师, Infosys Technologies Limited

    2004 年 7 月 13 日

    • +内容

    Java 语言中常被忽视的一个方面是它被归类为一种命令式(imperative)编程语言。命令式编程虽然由于与 Java 语言的关联而相当普及,但是并不是惟一可用的编程风格,也不总是最有效的。在本文中,我将探讨在 Java 开发实践中加入不同的编程方法 ── 即函数编程(FP)。

    命令式编程是一种用程序状态描述计算的方法。使用这种范型的编程人员用语句改变程序状态。这就是为什么,像 Java 这样的程序是由一系列让计算机执行的命令 (或者语句) 所组成的。 另一方面, 函数编程是一种强调表达式的计算而非命令的执行的一种编程风格。表达式是用函数结合基本值构成的,它类似于用参数调用函数。

    本文将介绍函数编程的基本特点,但是重点放在两个特别适用于 Java 开发框架的元素:闭包和高阶函数。如果您曾经使用过像 Python、Ruby 或者 Groovy (请参阅 参考资料) 这样的敏捷开发语言,那么您就可能已经遇到过这些元素。在这里,您将看到在 Java 开发框架中直接使用它们会出现什么情况。我将首先对函数编程及其核心元素做一个简短的、概念性的综述,然后用常用的编程场景展示,用结构化的方式使用闭包和高阶函数会给 Java 代码带来什么好处。

    什么是函数编程?

    在经常被引用的论文 “Why Functional Programming Matters”(请参阅 参考资料) 中,作者 John Hughes 说明了模块化是成功编程的关键,而函数编程可以极大地改进模块化。在函数编程中,编程人员有一个天然框架用来开发更小的、更简单的和更一般化的模块, 然后将它们组合在一起。函数编程的一些基本特点包括:

    • 支持闭包和高阶函数。
    • 支持懒惰计算(lazy evaluation)。
    • 使用递归作为控制流程的机制。
    • 加强了引用透明性。
    • 没有副作用。

    我将重点放在在 Java 语言中使用闭包和高阶函数上,但是首先对上面列出的所有特点做一个概述。

    闭包和高阶函数

    函数编程支持函数作为第一类对象,有时称为 闭包或者 仿函数(functor)对象。实质上,闭包是起函数的作用并可以像对象一样操作的对象。与此类似,FP 语言支持 高阶函数。高阶函数可以用另一个函数(间接地,用一个表达式) 作为其输入参数,在某些情况下,它甚至返回一个函数作为其输出参数。这两种结构结合在一起使得可以用优雅的方式进行模块化编程,这是使用 FP 的最大好处。

    命令式编程

    命令式编程这个名字是从自然语言(比如英语)的 祈使语气(imperative mood)衍生出来的,在这种语气中宣布命令并按照执行。除 Java 语言之外,C 和 C++ 是另外两种广泛使用的、符合命令式风格的高级编程语言。

    懒惰计算

    除了高阶函数和仿函数(或闭包)的概念,FP 还引入了 懒惰计算的概念。在懒惰计算中,表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值时进行计算。延迟的计算使您可以编写可能潜在地生成无穷输出的函数。因为不会计算多于程序的其余部分所需要的值,所以不需要担心由无穷计算所导致的 out-of-memory 错误。一个懒惰计算的例子是生成无穷 Fibonacci 列表的函数,但是对 第 n 个Fibonacci 数的计算相当于只是从可能的无穷列表中提取一项。

    递归

    FP 还有一个特点是用递归做为控制流程的机制。例如,Lisp 处理的列表定义为在头元素后面有子列表,这种表示法使得它自己自然地对更小的子列表不断递归。

    关于实现库

    我使用了由 Apache Commons Functor 项目提供的库构建本文使用的例子。Apache Commons Functor 库包括大量基本构造,可以在涉及闭包和高阶函数的复杂使用场景中重复使用。当然,可以使用不同的实现(如 Java Generic Libraries、Mango 或者 Generic Algorithms for Java),而不会对在本文中所讨论和展示的概念有影响,尽管您必须下载和使用 Apache Commons Functor 库才能演示这里的例子。

    引用透明性

    函数程序通常还加强 引用透明性,即如果提供同样的输入,那么函数总是返回同样的结果。就是说,表达式的值不依赖于可以改变值的全局状态。这使您可以从形式上推断程序行为,因为表达式的意义只取决于其子表达式而不是计算顺序或者其他表达式的副作用。这有助于验证正确性、简化算法,甚至有助于找出优化它的方法。

    副作用

    副作用是修改系统状态的语言结构。因为 FP 语言不包含任何赋值语句,变量值一旦被指派就永远不会改变。而且,调用函数只会计算出结果 ── 不会出现其他效果。因此,FP 语言没有副作用。

    这些基本描述应足以让您完成本文中的函数编程例子。有关这个主题的更多参考资料请参阅 参考资料一节。

    Java 语言中的函数编程

    不管是否相信,在 Java 开发实践中您可能已经遇到过闭包和高阶函数,尽管当时您可能没有意识到。例如,许多 Java 开发人员在匿名内部类中封闭 Java 代码的一个词汇单元(lexical unit)时第一次遇到了 闭包。这个封闭的 Java 代码单元在需要时由一个 高阶函数执行。例如,清单 1 中的代码在一个类型为 java.lang.Runnable的对象中封闭了一个方法调用。

    清单 1. 隐藏的闭包
     Runnable worker = new Runnable() 
     { 
      public void run() 
      { 
        parseData(); 
      } 
     };

    方法 parseData确实 封闭(因而有了名字 “闭包”)在 Runnable对象的实例 worker中。它可以像数据一样在方法之间传递,并可以在任何时间通过发送消息(称为 run) 给 worker对象而执行。

    更多的例子

    另一个在面向对象世界中使用闭包和高阶函数的例子是 Visitor 模式。如果还不熟悉这种模式,请参阅 参考资料以了解更多有关它的内容。基本上,Visitor 模式展现一个称为 Visitor 的参与者,该参与者的实例由一个复合对象(或者数据结构)接收,并应用到这个数据结构的每一个构成节点。Visitor 对象实质上 封闭了处理节点 / 元素的逻辑,使用数据结构的 accept (visitor)方法作为应用逻辑的高阶函数。

    通过使用适当不同的 Visitor 对象(即闭包),可以对数据结构的元素应用完全不同的处理逻辑。与此类似,可以向不同的高阶函数传递同样的闭包,以用另一种方法处理数据结构(例如,这个新的高阶函数可以实现不同逻辑,用于遍历所有构成元素)。

    类 java.utils.Collections提供了另一个例子,这个类在版本 1.2 以后成为了 Java 2 SDK 的一部分。它提供的一种实用程序方法是对在java.util.List中包含的元素排序。不过,它使调用者可以将排序列表元素的逻辑封装到一个类型为 java.util.Comparator的对象中,其中Comparator对象作为第二个参数传递给排序方法。

    在内部, sort方法的引用实现基于 合并 - 排序(merge-sort)算法。它通过对顺序中相邻的对象调用 Comparator对象的 compare方法遍历列表(list)中的元素。在这种情况下, Comparator是闭包,而 Collections.sort方法是高阶函数。这种方式的好处是明显的:可以根据在列表中包含的不同对象类型传递不同的 Comparator对象(因为如何比较列表中任意两个对象的逻辑安全地封装在 Comparator对象中)。与此类似, sort方法的另一种实现可以使用完全不同的算法(比如说, 快速排序(quick-sort)) 并仍然重复使用同样的 Comparator对象,将它作为基本函数应用到列表中两个元素的某种组合。

    创建闭包

    广意地说,有两种生成闭包的技术,使用闭包的代码可以等效地使用这两种技术。创建闭包后,可以以统一的方式传递它,也可以向它发送消息以让它执行其封装的逻辑。因此,技术的选择是偏好的问题,在某些情况下也与环境有关。

    最后一次要求下载!

    从这以后的讨论将结合基于 Apache Commons Functor 库的例子。如果您还没有下载这个库,应当 现在就下载。我将假定您可以访问 Javadocs 所带的 Apache 库,因此对单独的库类不再做过多的说明。

    在第一种技术 表达式特化(expression specialization)中,由基础设施为闭包提供一个一般性的接口,通过编写这个接口的特定实现创建具体的闭包。在第二种技术 表达式合成(expression composition)中,基础设施提供实现了基本一元 / 二元 / 三元 /.../n 元操作(比如一元操作符 not和二元操作符 andor)的具体帮助类。在这里,新的闭包由这些基本构建块的任意组合创建而成。

    我将在下面的几节中详细讨论这两种技术。

    表达式特化

    假定您在编写一个在线商店的应用程序。商店中可提供的商品用类 SETLItem表示。每一件商品都有相关的标签价格, SETLItem类提供了名为getPrice的方法,对商品实例调用这个方法时,会返回该商品的标签价格。

    如何检查 item1的成本是否不低于 item2呢?在 Java 语言中,一般要编写一个像这样的表达式:

     assert(item1.getPrice() >= item2.getPrice());

    像这样的表达式称为 二元谓词(binary predicate), 二元是因为它取两个参数,而 谓词是因为它用这两个参数做一些事情并生成布尔结果。不过要注意,只能在执行流程中执行上面的表达式,它的输出取决于 item1和 item2在特定瞬间的值。从函数编程的角度看,这个表达式还不是一般性的逻辑,就是说,它不能不管执行控制的当前位置而随心所欲地传递并执行。

    为了使二元谓词发挥作用,必须将它封装到一个对象中,通过 特化(specializing)一个称为 BinaryPredicate的接口做到这一点,这个接口是由 Apache Functor 库提供的,如清单 2 所示。

    清单 2. 表达式特化方法
     package com.infosys.setl.fp; 
     public class SETLItem 
     { 
      private String name; 
      private String code; 
      private int price; 
      private String category; 
    		
      public int getPrice() 
      { 
    	 return price; 
      } 
    	
      public void setPrice(int inPrice) 
      { 
    	 price = inPrice; 
      } 
    	
      public String getName() 
      { 
    	 return name; 
      } 
    	
      public void setName(String inName) 
      { 
    	 name = inName; 
      } 
      public String getCode() 
      { 
    	 return code; 
      } 
    	
      public void setCode(String inCode) 
      { 
    	 code = inCode; 
      } 
    	
      public String getCategory() 
      { 
    	 return category; 
      } 
    	
      public void setCategory(String inCategory) 
      { 
    	 category = inCategory; 
      } 
     } 
     package com.infosys.setl.fp; 
     import java.util.Comparator; 
     public class PriceComparator implements Comparator 
     { 
      public int compare (Object o1, Object o2) 
      { 
    	 return (((SETLItem)o1).getPrice()-((SETLItem)o2).getPrice()); 
      } 	
     } 
     package com.infosys.setl.fp; 
     import org.apache.commons.functor.*; 
     import org.apache.commons.functor.core.comparator.*; 
     import java.util.*; 
     public class TestA 
     { 
      public static void main(String[] args) 
      { 
    	 try 
    	 { 
    	  Comparator pc = new PriceComparator(); 
    	  BinaryPredicate bp = new IsGreaterThanOrEqual(pc); 
    	  SETLItem item1 = new SETLItem(); 
    	  item1.setPrice(100); 
    	  SETLItem item2 = new SETLItem(); 
    	  item2.setPrice(99); 
    	  if (bp.test(item1, item2)) 
    	    System.out.println("Item1 costs more than Item2!"); 
    	  else 
    	    System.out.println("Item2 costs more than Item1!"); 
    	  SETLItem item3 = new SETLItem(); 
    	  item3.setPrice(101); 
    	  if (bp.test(item1, item3)) 
    	    System.out.println("Item1 costs more than Item3!"); 
    	  else 
      	    System.out.println("Item3 costs more than Item1!"); 
    	 } 
    	 catch (Exception e) 
    	 { 
    	  e.printStackTrace(); 
    	 } 
      } 
     }

    BinaryPredicate接口以由 Apache Functor 库提供的 IsGreaterThanOrEqual类的形式特化。 PriceComparator类实现了java.util.Comparator接口,并被作为输入传递给 IsGreaterThanOrEqual类。收到一个 test消息时, IsGreaterThanOrEqual类自动调用PriceComparator类的 compare方法。 compare方法预期接收两个 SETLItem对象,相应地它返回两个商品的价格差。 compare方法返回的正值表明 item1的成本不低于 item2

    初看之下,对一个相当基本的操作要做很多的工作,那它有什么好处呢?特化 BinaryPredicate接口(而不是编写 Java 比较表达式) 使您无论在何时何地都可以比较任意两个商品的价格。可以将 bp对象作为数据传递并向它发送消息,以在任何时候、使用这两个参数的任何值来执行它(称为 test)。

    表达式合成

    表达式合成是得到同样结果的一种稍有不同的技术。考虑计算特定 SETLItem的净价问题,要考虑当前折扣和销售税率。清单 3 列出了这个问题基于仿函数的解决方式。

    清单 3. 表达式合成方法
     package com.infosys.setl.fp; 
     import org.apache.commons.functor.BinaryFunction; 
     import org.apache.commons.functor.UnaryFunction; 
     import org.apache.commons.functor.adapter.LeftBoundFunction; 
     public class Multiply implements BinaryFunction 
     { 
      public Object evaluate(Object left, Object right) 
      { 
    	 return new Double(((Double)left).doubleValue() * ((Double)right).doubleValue()); 
      } 
     } 
     package com.infosys.setl.fp; 
     import org.apache.commons.functor.*; 
     import org.apache.commons.functor.core.composite.*; 
     import org.apache.commons.functor.adapter.*; 
     import org.apache.commons.functor.UnaryFunction; 
     public class TestB 
     { 
      public static void main(String[] args) 
      { 
    	 try 
    	 { 
      	  double discountRate = 0.1; 
    	  double taxRate=0.33; 
    	  SETLItem item = new SETLItem(); 
    	  item.setPrice(100); 
    	  UnaryFunction calcDiscountedPrice = 
    	  new RightBoundFunction(new Multiply(), new Double(1-discountRate)); 
    	  UnaryFunction calcTax = 
    	  new RightBoundFunction(new Multiply(), new Double(1+taxRate)); 
    	  CompositeUnaryFunction calcNetPrice = 
    	  new CompositeUnaryFunction(calcTax, calcDiscountedPrice); 
    	  Double netPrice = (Double)calcNetPrice.evaluate(new Double(item.getPrice())); 
    	  System.out.println("The net price is: " + netPrice); 
    	 } 
    	 catch (Exception e) 
    	 { 
    	  e.printStackTrace(); 
    	 } 
      } 
     }

    BinaryFunction类似于前面看到的 BinaryPredicate,是一个由 Apache Functor 提供的一般化仿函数(functor)接口。 BinaryFunction接口有两个参数并返回一个 Object值。类似地, UnaryFunction是一个取一个 Object参数并返回一个 Object值的仿函数接口。

    RightBoundFunction是一个由 Apache 库提供的适配器类,它通过使用常量右参数(right-side argument)将 BinaryFunction适配给UnaryFunction接口。即,在一个参数中收到相应的消息( evaluate) 时,它在内部用两个参数发送一个 evaluate消息给正在适配的BinaryFunction── 左边的是发送给它的参数,右边的是它知道的常量。您一定会猜到,名字 RightBoundFunction来自于常量值是作为第二个 (右边) 参数传递这一事实。(是的,Apache 库还提供了一个 LeftBoundFunction,其中常量是作为第一个参数或者左参数传递的。)

    用于双精度相乘的特化的 BinaryFunction

    清单 3 显示了名为 Multiply的特化的 BinaryFunction,它取两个 Double作为输入并返回一个新的、由前两个双精度值相乘而得到 Double

    在 calcDiscountedRate中实例化了一个新的 RightBoundFunction,它通过用 (1 - discountRate)作为其常量第二参数,将二元 Multiply函数适配为一元接口。

    结果,可以用一个 Double参数向 calcDiscountRate发送一个名为 evaluate的消息。在内部,输入参数 Double乘以 calcDiscountRate对象本身包含的常量值。

    与此类似,在 calcTaxRate中实例化了一个新的 RightBoundFunction,它通过用 (1 + taxRate)作为其第二个常量参数将二元 Multiply函数适配为一元接口。结果,可以用一个 Double参数向 calcTaxRate发送一个名为 evaluate的消息。在内部,输入参数 Double乘以calcTaxRate对象本身包含的常量值。

    这种将多参数的函数重新编写为一个参数的函数的合成(composition)技术也称为 currying

    合成魔术在最后的时候就发挥作用了。实质上,计算对象净价的算法是首先计算折扣价格(使用 calcDiscountRate仿函数),然后通过在上面加上销售税(用 calcSalesTax仿函数)计算净价。就是说,需要组成一个函数,在内部调用第一个仿函数并将计算的输出流作为第二个仿函数的计算的输入。Apache 库提供了用于这种目的的一个内置仿函数,称为 CompositeUnaryFunction

    在清单 3 中, CompositeUnaryFunction实例化为变量 calcNetPrice,作为 calcDiscountRate和 calcSalesTax仿函数的合成。与前面一样,将可以向其他函数传递这个对象,其他函数也可以通过向它发送一个包含商品参数的 evaluate消息要求它计算这种商品的净价。

    一元与二元合成

    在清单 3 中,您看到了 一元合成的一个例子,其中一个一元仿函数的结果是另一个的输入。另一种合成称为 二元合成,作为 evaluate消息的一部分,需要传递两个一元仿函数的结果作为二元仿函数的参数。

    清单 4 是说明二元合成的必要性和风格的一个例子。假定希望保证商店可以给出的最大折扣有一个最大限度。因此,必须将作为calcDiscount仿函数计算结果得到的折扣量与 cap 值进行比较,并取最小值作为计算出的折扣价格。折扣价格是通过用标签价减去实际的折扣而计算的。

    清单 4. 二元合成
     package com.infosys.setl.fp; 
    import org.apache.commons.functor.BinaryFunction; 
    
    public class Subtract implements BinaryFunction 
    { 
        public Object evaluate(Object left, Object right) 
        { 
            return new Double(((Double)left).doubleValue()-((Double)right).doubleValue());
        } 
    } 
    
    package com.infosys.setl.fp; 
    import org.apache.commons.functor.BinaryFunction; 
    import org.apache.commons.functor.UnaryFunction; 
    
    public class BinaryFunctionUnaryFunction implements UnaryFunction 
    { 
        private BinaryFunction function; 
    	
        public BinaryFunctionUnaryFunction(BinaryFunction f) 
        { 
            function=f; 	
        } 
    	
        public Object evaluate(Object obj) 
        { 
            return function.evaluate(obj,obj); 
        } 
    } 
    
    package com.infosys.setl.fp; 
    import org.apache.commons.functor.*; 
    import org.apache.commons.functor.core.composite.*; 
    import org.apache.commons.functor.adapter.*; 
    import org.apache.commons.functor.UnaryFunction; 
    import org.apache.commons.functor.core.Constant; 
    import org.apache.commons.functor.core.comparator.Min; 
    
    public class TestC 
    { 
        public static void main(String[] args) 
        { 
            double discountRate = 0.1; 
            double taxRate=0.33; 
            double maxDiscount = 30; 
            SETLItem item = new SETLItem(); 
            item.setPrice(350); 
            UnaryFunction calcDiscount = 
                new RightBoundFunction(new Multiply(), new Double(discountRate)); 
            Constant cap = new Constant(new Double(maxDiscount)); 
            BinaryFunction calcActualDiscount = 
                new UnaryCompositeBinaryFunction (new Min(), calcDiscount, cap); 
            BinaryFunctionUnaryFunction calcActualDiscountAsUnary = 
                new BinaryFunctionUnaryFunction(calcActualDiscount); 
            BinaryFunction calcDiscountedPrice = 
                new UnaryCompositeBinaryFunction (new Subtract(), new Identity()
    	    , calcActualDiscountAsUnary); 
            BinaryFunctionUnaryFunction calcDiscountedPriceAsUnary = 
                new BinaryFunctionUnaryFunction(calcDiscountedPrice); 	
            UnaryFunction calcTax = 
                new RightBoundFunction(new Multiply(), new Double(1+taxRate)); 
            CompositeUnaryFunction calcNetPrice = 
                new CompositeUnaryFunction(calcTax, calcDiscountedPriceAsUnary); 
            Double netPrice = (Double)calcNetPrice.evaluate(new Double(item.getPrice()));
            System.out.println("The net price is: " + netPrice); 
        } 
    }

    通过首先观察所使用的 Apache Functor 库中的三个标准仿函数,开始分析和理解这段代码。

    • UnaryCompositeBinaryFunction仿函数取一个二元函数和两个一元函数作为输入。首先计算后两个函数,它们的输出作为输入传递给二元函数。在清单 4 中对二元合成使用这个仿函数两次。
    • Constant仿函数的计算总是返回一个常量值(即在其构造时输入的值),不管以后任何计算消息中传递给它的参数是什么值。在清单 4 中,变量 cap的类型为 Constant并总是返回最大折扣数量。
    • Identity仿函数只是返回作为 evaluate消息的输入参数传递给它的这个对象作为输出。清单 4 显示 Identity仿函数的一个实例,该仿函数是在创建 calcDiscountedPrice时作为一个一元仿函数创建和传递的。同时在清单 4 中, evaluate消息包含标签价格作为其参数,这样Identity仿函数就返回标签价格作为输出。

    第一个二元合成在用计算 calcDiscount(通过对标签价格直接应用折扣率)和 cap的 UnaryCompositeBinaryFunction设置变量calcActualDiscount时是可见的。这两个一元仿函数计算的输出传递给称为 Min的内置二元仿函数,它比较这两者并返回其中最小的值。

    这个例子显示了定制类 BinaryFunctionUnaryFunction。这个类适配一个二元仿函数,使它像一元仿函数的接口。就是说,当这个类接收一个带有一个参数的 evaluate消息时,它在内部发送 (向其封装的二元函数)一个 evaluate消息,它的两个参数是作为输入接收的同一个对象。因为 calcActualDiscount是二元函数,所以通过类型为 BinaryFunctionUnaryFunction的 calcActualDiscountAsUnary实例将它包装到一个一元仿函数接口中。很快就可以看到包装 calcActualDiscount为一元仿函数的理由。

    当用 UnaryCompositeBinaryFunction设置变量 calcDiscountedPrice时发生第二个二元合成。 UnaryCompositeBinaryFunction向新的Identity实例和 calcActualDiscountAsUnary对象发送 evaluation消息,这两个消息的输入参数都是标签价格。

    这两个计算(它们分别得出标签价格和实际的折扣值)的输出传递给名为 Subtract的定制二元仿函数。当向后一个对象发送 evaluate消息时,它立即计算并返回两个参数之间的差距(这是商品的折扣价)。这个二元仿函数也用定制的 BinaryFunctionUnaryFunction包装为一个名为 calcDiscountedPriceAsUnary的一元仿函数对象。

    与前面的情况一样,代码通过两个 calcTax一元仿函数(也在清单 3 中遇到)和 calcDiscountedPriceAsUnary(在前面一段中描述)创建CompositeUnaryFunction,而以一个一元合成完成。这样得到的 calcNetPrice变为接收一个 evaluate消息和一个参数(所讨论商品的标签价格),而在内部,首先用这个参数计算 calcDiscountedPriceAsUnary仿函数,然后用前一个计算的输出作为参数计算 calcTax仿函数。

    使用闭包实现业务规则

    Apache Library 提供了各种不同的内置一元和二元仿函数,它使得将业务逻辑编写为可以传递并且可以用不同的参数在不同的位置执行的对象变得非常容易。在后面几节中,我将使用一个简单的例子展示对一个类似问题的函数编程方式。

    假定一个特定的商品是否可以有折扣取决于该商品的类别和定价。具体说,只有 Category “A” 中定价高于 100 美元和 Category “B“ 中定价高于 200 美元的商品才有资格打折。清单 5 中的代码显示了一个名为 isEligibleForDiscount的业务规则对象 ( UnaryPredicate),如果用一个item对象作为参数发送 evaluate消息,将返回一个表明是否可以对它打折的 Boolean。

    清单 5. 一个函数业务规则对象
    package com.infosys.setl.fp; 
    import org.apache.commons.functor.BinaryPredicate; 
    import org.apache.commons.functor.UnaryPredicate; 
    public class BinaryPredicateUnaryPredicate implements UnaryPredicate 
    { 
        private BinaryPredicate bp; 
    	
        public BinaryPredicateUnaryPredicate(BinaryPredicate prd) 
        { 
            bp=prd; 	
        } 
    	
        public boolean test(Object obj) 
        { 
            return bp.test(obj,obj); 
        } 
    } 
    
    package com.infosys.setl.fp; 
    import org.apache.commons.functor.*; 
    import org.apache.commons.functor.core.composite.*; 
    import org.apache.commons.functor.adapter.*; 
    import org.apache.commons.functor.UnaryFunction; 
    import org.apache.commons.functor.core.Constant; 
    import org.apache.commons.functor.core.IsEqual; 
    import org.apache.commons.functor.core.comparator.IsGreaterThanOrEqual; 
    import org.apache.commons.functor.core.comparator.Min; 
    import org.apache.commons.functor.core.Identity; 
    public class TestD 
    { 
        public static void main(String[] args) 
        { 		
            SETLItem item1 = new SETLItem(); 
            item1.setPrice(350); 
            item1.setCategory("A"); 
            SETLItem item2 = new SETLItem(); 
            item2.setPrice(50); 
            item2.setCategory("A"); 
            SETLItem item3 = new SETLItem(); 
            item3.setPrice(200); 
            item3.setCategory("B"); 
    		
            UnaryFunction getItemCat = 
            new UnaryFunction() 
            { 
                public Object evaluate (Object obj) 
                { 
                    return ((SETLItem)obj).getCategory(); 
                } 
            }; 
      	 
            UnaryFunction getItemPrice = 
                new UnaryFunction() 
            { 
                public Object evaluate  (Object obj) 
                { 
                    return new Double(((SETLItem)obj).getPrice()); 
                } 
            }; 
    		
            Constant catA = new Constant("A"); 
            Constant catB = new Constant("B"); 
            Constant usd100 = new Constant(new Double(100)); 
            Constant usd200 = new Constant(new Double(200)); 
    		
            BinaryPredicateUnaryPredicate belongsToCatA = new BinaryPredicateUnaryPredicate
                (new UnaryCompositeBinaryPredicate(new IsEqual(), getItemCat, catA)); 
            BinaryPredicateUnaryPredicate belongsToCatB = new BinaryPredicateUnaryPredicate
                (new UnaryCompositeBinaryPredicate(new IsEqual(), getItemCat, catB)); 
    		
            BinaryPredicateUnaryPredicate moreThanUSD100 = new BinaryPredicateUnaryPredicate
                (new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual(), 
    	    getItemPrice, usd100)); 
            BinaryPredicateUnaryPredicate moreThanUSD200 = new BinaryPredicateUnaryPredicate
                (new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual(), 
    	    getItemPrice, usd200)); 
    		
            UnaryOr isEligibleForDiscount = new UnaryOr(new UnaryAnd(belongsToCatA, 
    	    moreThanUSD100), 
                new UnaryAnd(belongsToCatB, moreThanUSD200)); 
            if (isEligibleForDiscount.test(item1)) 
                System.out.println("Item #1 is eligible for discount!"); 
            else 
                System.out.println("Item #1 is not eligible for discount!"); 
    			
            if (isEligibleForDiscount.test(item2)) 
                System.out.println("Item #2 is eligible for discount!"); 
            else 
                System.out.println("Item #2 is not eligible for discount!"); 
            
    	if (isEligibleForDiscount.test(item3)) 
                System.out.println("Item #3 is eligible for discount!"); 
            else 
                System.out.println("Item #3 is not eligible for discount!");
        } 
    }

    使用 ComparableComparator

    清单 5 中可能注意到的第一件事是我利用了名为 isEqual(用于检查所说商品的类别是否等于 “A”或者“B ”) 和 isGreaterThanOrEqual(用于检查所述商品的定价是否大于或者等于指定值,对于 Category “A” 商品是 100,对于 Category “B” 商品是 200) 的内置二元谓词仿函数。

    您可能还记得在 清单 2中,原来必须传递 PriceComparator对象(封装了比较逻辑)以使用 isGreaterThanOrEqual仿函数进行价格比较。不过在清单 5 中,不显式传递这个 Comparator对象。如何做到不需要它? 技巧就是,在没有指定该对象时, isGreaterThanOrEqual仿函数(对这一点,甚至是 IsEqual仿函数)使用默认的 ComparableComparator。这个默认的 Comparator假定两个要比较的对象实现了java.lang.Comparable接口,并对第一个参数(在将它类型转换为 Comparable后)只是调用 compareTo方法,传递第二个参数作为这个方法的参数。

    通过将比较工作委派给这个对象本身,对于 String 比较(像对 item目录所做的)和 Double比较(像对 item价格所做的),可以原样使用默认的 Comparator。 String和 Double都是实现了 Comparable接口的默认 Java 类型。

    将二元谓词适配为一元

    可能注意到的第二件事是我引入了一个名为 BinaryPredicateUnaryPredicate的新仿函数。这个仿函数(类似于在 清单 4中第一次遇到的BinaryFunctionUnaryFunction仿函数)将一个二元谓词接口适配为一元接口。 BinaryPredicateUnaryPredicate仿函数可以认为是一个带有一个参数的一元谓词:它在内部用同一个参数的两个副本计算包装的二元谓词。

    isEligibleForDiscount对象封装了一个完整的业务规则。如您所见,它的构造方式 ── 即,通过将构造块从下到上放到一起以构成更复杂的块,再将它们放到一起以构成更复杂的块,等等 ── 使它本身天然地成为某种“可视化的”规则构造器。最后的规则对象可以是任意复杂的表达式,它可以动态地构造,然后传递以计算底层业务规则。

    对集合操作

    GoF Iterator 模式(请参阅 参考资料) 提供了不公开其底层表示而访问集合对象的元素的方法。这种方法背后的思路是迭代与数据结构不再相关联 (即它不是集合的一部分)。这种方式本身要使用一个表示集合中特定位置的对象,并用一个循环条件 (在集合中增加其逻辑位置)以遍历集合中所有元素。循环体中的其他指令可以检查和 / 或操作集合中当前 Iterator对象位置上的元素。在本例中,我们对迭代没有什么控制。(例如,必须调用多少次 next、每次试图访问 next元素时必须首先检查超出范围错误。) 此外,迭代器必须使用与别人一样的公共接口访问底层数据结构的“成员”,这使得访问效率不高。这种迭代器常被称为 “外部迭代器(External Iterator)”。

    FP 对这个问题采取了一种非常不同的方式。集合类有一个高阶函数,后者以一个仿函数作为参数并在内部对集合的每一个成员应用它。在本例中,因为迭代器共享了数据结构的实现,所以您可以完成控制迭代。此外,迭代很快,因为它可以直接访问数据结构成员。这种迭代器常被称为 内部迭代器(internal Iterator)

    Apache Functor 库提供了各种非严格地基于 C++ 标准模板库实现的内部 Iterator。它提供了一个名为 Algorithms的 实用工具类,这个类有一个名为 foreach的方法。 foreach方法以一个 Iterator对象和一个一元 Procedure作为输入,并对遍历 Iterator时遇到的每一个元素(元素本身是作为过程的一个参数传递的)运行一次。

    使用内部迭代器

    一个简单的例子将可以说明外部和内部 Iterator的不同。假定提供了一组 SETLItem对象并要求累积列表中成本高于 200 美元的那些商品的定价。清单 6 展现了完成这一工作的代码。

    清单 6. 使用外部和内部迭代器
    package com.infosys.setl.fp; 
    import java.util.*; 
    import org.apache.commons.functor.Algorithms; 
    import org.apache.commons.functor.UnaryFunction; 
    import org.apache.commons.functor.UnaryProcedure; 
    import org.apache.commons.functor.core.Constant; 
    import org.apache.commons.functor.core.collection.FilteredIterator; 
    import org.apache.commons.functor.core.comparator.IsGreaterThanOrEqual; 
    import org.apache.commons.functor.core.composite.UnaryCompositeBinaryPredicate; 
    public class TestE 
    { 
        public static void main(String[] args) 
        { 		
            Vector items = new Vector(); 
            for (int i=0; i<10; i++) 
            { 
                SETLItem item = new SETLItem(); 
                if (i%2==0) 
                    item.setPrice(101); 
                else 
                    item.setPrice(i); 
                items.add(item); 
            } 
            TestE t = new TestE(); 	
            System.out.println("The sum calculated using External Iterator is: " + 
                t.calcPriceExternalIterator(items)); 
            System.out.println("The sum calculated using Internal Iterator is: " + 
                t.calcPriceInternalIterator(items)); 
     } 
    	
        public int calcPriceExternalIterator(List items) 
        { 
            int runningSum = 0; 
            Iterator i = items.iterator(); 
            while (i.hasNext()) 
            { 
                int itemPrice = ((SETLItem)i.next()).getPrice(); 
                if (itemPrice >= 100) 
                    runningSum += itemPrice; 
            } 
            return runningSum; 
        } 
    	
        public int calcPriceInternalIterator(List items) 
        { 
            Iterator i = items.iterator(); 
            UnaryFunction getItemPrice = 
            new UnaryFunction() 
            { 
                public Object evaluate (Object obj) 
                { 
                    return new Double(((SETLItem)obj).getPrice()); 
                } 
            }; 
            Constant usd100 = new Constant(new Double(100)); 
            BinaryPredicateUnaryPredicate moreThanUSD100 = new BinaryPredicateUnaryPredicate
                (new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual(), 
    	    getItemPrice, usd100)); 
            FilteredIterator fi = new FilteredIterator(i, moreThanUSD100); 
            Summer addPrice = new Summer(); 
            Algorithms.foreach(fi, addPrice); 
            return addPrice.getSum(); 
        } 
    	
        static class Summer implements UnaryProcedure 
        { 
            private int sum=0; 
            public void run(Object obj) 
            { 
                sum += ((SETLItem)obj).getPrice(); 
            } 
            public int getSum() 
            { 
                return sum;
            } 
        } 
    }

    在 main()方法中,设置一个有 10 种商品的列表,其中奇数元素的价格为 101 美元。(在真实应用程序中,将使用调用 JDBC 获得的ResultSet而不是本例中使用的 Vector。)

    然后用两种不同的方法对列表执行所需要的操作: calcPriceExternalIterator和 calcPriceInternalIterator。正如其名字所表明的,前者基于 ExternalIterator而后者基于 InternalIterator。您将关心后一种方法,因为所有 Java 开发人员都应该熟悉前者。注意InternalIterator方法使用由 Apache Functor 库提供的两个结构。

    第一个结构称为 FilteredIterator。它取一个迭代器加上一个一元 谓词作为输入,并返回一个带有所感兴趣的属性的 Iterator。这个属性给出了在遍历 Iterator时遇到的每一个满足在 谓词中规定的条件的元素。(因此由 FilteredIterator的一个实例返回的 Iterator 可以作为FilteredIterator的第二个实例的输入传递,以此类推,以设置过滤器链,用于根据多种标准分步挑出元素。)在本例中,只对满足 一元谓词“大于或等于 100 美元”规则的商品感兴趣。这种规则是在名为 moreThanUSD100的 BinaryPredicateUnaryPredicate中规定的,我们在 清单 5中第一次遇到了它。

    Apache Functor 库提供的第二个结构是名为 Algorithms的实用程序类,在 前面描述过这个类。在这个例子中,名为 Summer的一元过程只是包含传递给它的 SETLItem实例的定价,并将它添加到(本地)运行的 total 变量上。这是一个实现了前面讨论的内部迭代器概念的类。

    使用仿函数进行集合操纵

    我讨论了用仿函数和高阶函数编写模块的大量基础知识。我将用最后一个展示如何用仿函数实现集合操纵操作的例子作为结束。

    通常,有两种描述集合成员关系的方式。第一种是完全列出集合中的所有元素。这是 Java 编程人员传统上使用的机制 ── java.util.Set接口提供了一个名为 add(Object)的方法,如果作为参数传递到底层集合中的对象还未存在的话,该方法就添加它。

    不过,当集合中的元素共享某些公共属性时,通过声明惟一地标识了集合中元素的属性可以更高效地描述集合的成员关系。例如,后一种解决方案适合于集合成员的数量很大,以致不能在内存中显式地维护一个集合实现(像前一种方式那样)的情况。

    在这种情况下,可以用一个一元 谓词表示这个集合。显然,一个一元 谓词隐式定义了一组可以导致谓词计算为 true的所有值(对象)。事实上,所有集合操作都可以用不同类型的谓词组合来定义。清单 7 中展示了这一点:

    清单 7. 使用仿函数的集合操作
     package com.infosys.setl.fp; 
     import org.apache.commons.functor.UnaryPredicate; 
     import org.apache.commons.functor.core.composite.UnaryAnd; 
     import org.apache.commons.functor.core.composite.UnaryNot; 
     import org.apache.commons.functor.core.composite.UnaryOr; 
     public class SetOps 
     { 
      public static UnaryPredicate union(UnaryPredicate up1, UnaryPredicate up2) 
      { 
    	 return new UnaryOr(up1, up2); 
      } 
    	
      public static UnaryPredicate intersection(UnaryPredicate up1, UnaryPredicate up2) 
      { 
    	 return new UnaryAnd(up1, up2); 
      } 
    	
      public static UnaryPredicate difference(UnaryPredicate up1, UnaryPredicate up2) 
      { 
    	 return new UnaryAnd(up1, new UnaryNot(up2)); 
      } 
    	
      public static UnaryPredicate symmetricDifference(UnaryPredicate up1, 
        UnaryPredicate up2) 
      { 
    	 return difference(union(up1, up2), intersection(up1, up2)); 
      } 
     }

    用一元 谓词来描述集合 并集(union)和 交集(intersection)操作的定义应当是明确的:如果一个对象至少使指示两个集合的两个一元 谓词中的一个计算为 true,那么这个对象就属于两个集合的并集(逻辑 Or);如果它使两个一元 谓词都计算为 true,那么它就属于两个集合的交集(逻辑 And)。

    两个集合的差在数学上定义为属于第一个集合但是不属于第二个集合一组元素。根据这个定义,静态方法 difference也容易理解。

    最后,两个集合的对称差(symmetric difference)定义为只属于两个集合中的一个(或者两个都不属于)的所有元素。这可以取两个集合的并集,然后从中删除属于两个集合的交集的元素得到。就是说,它是对原来的集合(在这里是一元 谓词)使用 union和 intersection操作分别得到的两个集合的 difference操作。后一个定义解释了为什么用前三种方法作为第四个方法中的构建块。

    结束语

    模块化是任何平台上高生产率和成功的编程的关键,这一点早已被认识到了。Java 开发人员的问题是模块化编程不仅是将问题分解,它还要求能将小的解决方案粘接到一起,成为一个有效的整体。由于这种类型的开发继承了函数编程范型,在 Java 平台上开发模块化代码时使用函数编程技术应是很自然的事。

    在本文中,我介绍了两种函数编程技术,它们可以容易地结合到 Java 开发实践中。正如从这里看到的,闭包和高阶函数对于 Java 开发人员来说并不是完全陌生的,它们可以有效地结合以创建一些非常有用的模块化解决方案。

    我希望本文提供了在 Java 代码中结合闭包和高阶函数的很好基础,并使您见识到了函数编程的优美和效率。要学习有关本文所讨论的概念和技术的更多内容,请参阅 参考资料

    参考资料

    展开全文
  • Java 语言中函数编

    2006-09-27 23:44:00
    利用闭包和高阶函数编写模块化的 Java 代码级别: 初级Abhijit Belapurkar, 高级技术架构师, Infosys Technologies Limited2004 年 7 月 13 日如果您从事大型企业项目开发,您就会熟悉编写模块化代码的好处。...
    展开全文
  • 这是来自jQuery官方插件开发规范要求,使用这种编写方式有什么好处呢? a) 避免全局依赖。 b) 避免第三方破坏。 c) 兼容jQuery操作符’$’和’jQuery ‘ 二,有了闭包,在其中加入插件骨架 代码如下: $.fn.dBox...
  • jquery插件扩展

    2015-10-24 18:12:46
    一、封装代码,就是闭包,jquery插件的标准写法  这样写的好处: 避免全局依赖避免第三方破坏兼容jquery本身的操作符 “$”和“jQuery” 二、编写插件 三、使用自己的插件 四、运行结果
  • 当面试时 如何回答的一个技术记汇,...具体实现(再到具体实现的思路和对他代码一定的编写) ​ 5.还有没有更好的解决方案!(最后闭包的劣势的有哪些更好的就解决方法) 从这五个方面回答百分之八十以上被面试官认可
  • 如果需要并行的处理集合,需要编写客户端代码来处理,而不是集合本身处理。 在Java8中,引入闭包的目的就是为了给集合提供函数式的方法,方便处理。这样所带来的好处是,集合能够自己组织他们的元素,
  • js插件开发

    2016-06-09 21:32:46
    当我们画出了UI之后就可以正式编写jQuery插件代码了,不过在着之前我们还需要对jQuery插件开发一些规范性有一些了解。 1. 使用闭包: (function($) { // Code goes here })(jQuery); 这是来自jQuery官方...
  • jQuery插件开发

    2019-05-10 18:14:00
    这是来自jQuery官方插件开发规范要求,使用这种编写方式有什么好处呢? a) 避免全局依赖。 b) 避免第三方破坏。 c) 兼容jQuery操作符'$'和'jQuery ' 我们知道这段代码在被解析时会形同如下代码: ...
  • js插件开发规范

    2017-05-24 17:19:46
    当我们画出了UI之后就可以正式编写jQuery插件代码了,不过在着之前我们还需要对jQuery插件开发一些规范性有一些了解。 使用闭包: (function($) { // Code goes here })(jQuery);这是来自jQuery官方插件开发...
  • jQuery插件

    2014-04-01 13:48:39
    当我们画出了UI之后就可以正式编写jQuery插件代码了,不过在着之前我们还需要对jQuery插件开发一些规范性有一些了解。 1. 使用闭包: (function($) {  // Code goes here })(jQuery); 这是来自jQuery官方...
  • 当我们画出了UI之后就可以正式编写jQuery插件代码了,不过在着之前我们还需要对jQuery插件开发一些规范性有一些了解。   1. 使用闭包: (function($){ //Code goes here })(jQuery);  这是来自jQuery官方...
  • jQ插件开发规范(转)

    2015-07-13 17:32:00
    当我们画出了UI之后就可以正式编写jQuery插件代码了,不过在着之前我们还需要对jQuery插件开发一些规范性有一些了解。1. 使用闭包:(function($) {// Code goes here})(jQuery);这是来自jQuery官方插件开发规范...
  • 这是来自jQuery官方插件开发规范要求,使用这种编写方式有什么好处呢? a) 避免全局依赖。 b) 避免第三方破坏。 c) 兼容jQuery操作符'$'和'jQuery ' 我们知道这段代码在被解析时会形同如下代码:var jq = ...
  • jQuery基础知识1

    2018-07-03 19:00:02
    1.jQuery为WEB应用带来的最大的好处就是:无需自己编写大量的脚本代码就能实现大量启用脚本的行为;2.闭包:就是function实例,结合了来自环境的局部变量;3.找出分组中哪些元素被选中并选取其value值:(传统方法) ...
  • jquery插件制作

    2010-12-22 14:41:00
    本文仅供参考,这里以一个弹出层jQuery插件制作实例... 这是来自jQuery官方插件开发规范要求,使用这种编写方式有什么好处呢? a) 避免全局依赖。 b) 避免第三方破坏。 c) 兼容
  • c#学习笔记.txt

    热门讨论 2008-12-15 14:01:21
    换言之,基接口集是显式基接口、它们显式基接口(依此类推)完全可传递的闭包。接口继承其基接口所有成员。接口成员是通过 I.M 和 I[A] 形式成员访问和索引访问表达式访问,其中 I 是接口类型实例,M 是...
  • apidoc使用教程-编写漂亮api文档 yapi部署文档 React学习笔记_利用cors实现ajax跨域_Tomcat 获取跨域Authorization React学习笔记_ReactRedux应用使用AsyncAwait React学习笔记_动态注入reducer React...
  • 这种方案的好处在于你几乎不需要更改任何现有代码,而且兼容性也非常好,不管你使用的哪个Promise库,甚至是不同的Promise之间相互调用,都可以达到目的。 然而这个方案有一个不那么明显的...

空空如也

空空如也

1 2
收藏数 26
精华内容 10
关键字:

编写代码闭包的好处