首页 > 代码库 > 回调与Java8的λ表达式

回调与Java8的λ表达式

【9.3.1 回调】中,介绍了回调和<a target=_blank href=http://www.mamicode.com/"http://blog.csdn.net/yqj2065/article/details/8758101">好莱坞原则>


(Java中)回调与通常的(遵循OCP 代码的)非回调代码,使用的技术不过是动态绑定/多态。

回调与通常的非回调代码,从类图和其自身代码上,没有区别。

之所以需要回调callback、隐式调用Implicit invocation(某些软件架构的作者使用的术语),是因为分层结构的一条线的存在。

假定下层有IClient、Server:

package Lower;
@FunctionalInterface
public interface IClient {
     public void callback(int i);// 参数为底层上传的数据    
}

package Lower;
public class Server {
    private IClient whoCallMe; //必须获得一个IXxx的引用,由构造器的参数提供
    public Server(IClient event) {
        whoCallMe = event;
    }

    public void copy() {
        for(int i=0;i<100;i++){            
            if (i%10 == 0) {//在适当的时机调用回调
                whoCallMe.callback(i/10);
            }            
        } 
        System.out.println("copy() over");
    }    
}
上层Client需要更新进度条——显示复制任务完成的进度时,需要按照下层接口IClient定义的方法callback,给出自己的实现。

package Upper;
import Lower.*;
public class Client implements IClient {
    public void call() {
        new Server(this).copy();//传递this
    }

    //下层调用时传回一些数据。
    @Override
    public void callback(int i) {
        System.out.println("Upper:" + i + "0%");
    }

    public static void main(String[] a) {
        //Server
        new Client().call();        
    }

}
一个回调函数/方法(简称回调/ callback)是上层模块实现的,将被下层模块(反过来)调用的方法

3. 回调的实现

package Lower;
import java.util.List;
import java.util.ArrayList;

public class Server2 {
    private List<IClient> listeners = new ArrayList<>();//电话簿
    public void register(IClient listener) {//监听器注册
        listeners.add(listener);
    }
    public void copy() {
        for (IClient x : listeners) {
            for (int i = 0; i <= 100; i++) {   //在适当的时机调用回调
                if(i % 20 == 0) x.callback(i/10);// 通知所有已登记的演员
            }
        }
        System.out.println("copy() over");
    }
}

当下层模块状态发生某些变化时——通常由操作系统或JVM捕捉这种状态变化并调用回调函数,程序员最关心的是上层模块如何提供回调的方法体。最理想的方式是在注册时直接给出代码,如伪代码:

s.register(λi.(操作i)) //λ表达式
事实上,封装代码的callback(int)方法的方法名不需要存在(只需要参数和对参数的处理代码),更不用说封装callback(int)方法的类和对象。


还记得冯?诺依曼的存储-程序概念吗?可执行代码也被储存在内存中。从提供回调的方法体角度,在编程领域,
★回调通常指可以被作为参数传递给其他代码的可执行代码块,或者一个可执行代码的引用。
如果能够将可执行代码封装成方法如foo(),而方法名foo又可以作为其他方法的参数,则可以register(foo)实现回调。在JavaScript, Perl和 PHP中可以如此实现。
如果能够操作可执行代码的内存地址即函数指针(function pointers) 则可以像C或C++那样实现回调。

Java8的λ表达式,终于完成了回调的原意——代码的参数化,即doSth( foo )按照统一的形式,随着foo的不同使得doSth不同。

package Upper;
import Lower.*;
public class Client implements IClient {

    public void call() {
        new Server(this).copy();//传递this
    }

    //下层调用时传回一些数据。
    @Override
    public void callback(int i) {
        System.out.println("Upper:" + i + "0%");
    }

    public static void main(String[] a) {
        //Server2
//        Server2 s =new Server2();
//        s.register( new Client());
//        s.register( new Client());
//        s. copy();//这里由上层模块触发事件的发生
        
        //3 λ表达式Vs. Java匿名类(参见9.4.5节)
        Server2 s =new Server2();        
        IClient listener1=(i)->{System.out.println("+" + i + "0%");};
        s.register(listener1);
        s.register((i)->{System.out.println("++" + i + "0%");});
        s.register(new IClient(){
             @Override public void callback(int i){
                 System.out.println("==" + i + "0%");
             }
        });
        s. copy();        
    }
}

为了方便地使用Lambda表达式取代匿名类,Java8新引入了概念:函数接口(functional interface),即仅仅显式声明了一个自己的抽象方法的接口(可以用@FunctionalInterface标注)。

IClient listener1=(i)->{System.out.println("+" + i + "0%");};

λ表达式的类型,叫做“目标类型(target type)”,必须是函数接口。上面的赋值语句,将λ表达式——事实上是函数接口的实现类的引用(也可以理解为像C或C++那样的函数指针)赋值给函数接口。




当然,有了锤子,满世界就有太多的钉子。有的的确是钉子,有的则被当成了钉子。