本文首发于我的微信公众号锋起墨落(javaDeveloper2017),转载请注明出处。
封面拍摄于翼云石头部落,水流推动了水车,水车又反过来推动了水流……
什么是回调函数(callback):
维基百科定义: 在计算机程序设计中,回调函数,或简称回调,是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。
这段定义并不容易理解,不同语言的回调也有不同的表现形式,比如C/C++中回调是通过指针的形式实现的,javascript是通过传递函数作为参数来实现的,简单说来,java中的回调函是指一个类调用另一个类的方法,方法执行完成之后被调用者类会反过来调用调用者类方法(回调)的过程。
回调函数的作用:
程序模块之间的调用大致分为三种,同步调用,回调,以及异步调用。同步调用是阻塞式的,回调模式是一种双向的调用,异步调用可以解决同步阻塞问题。 回调是异步的基础,因此回调不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。
3经典回调函数示例:
我们举一个客户端-服务端(Client-Server)通讯的例子来阐述回调函数。 Client向Server发送一条消息,并立即打印出发送状态(无需等待服务端处理完成),服务端接收消息处理完成后通过回调通知客户端。
首先我们定义一个CallBack接口,接口中backMsg即为回调函数。
1 public interface CallBack { 2 public void backMsg(String msg); 3 }
客户端的需要实现回调接口,客户端通过sendMsg发送消息,通过回调函数backMsg来接收返回消息。
1 public class Client implements CallBack { 2 private Server server; 3 4 public Client(Server server) { 5 this.server = server; 6 } 7 8 /** 9 * 回调函数 10 * @param msg 11 */ 12 public void backMsg(String msg) { 13 System.out.println("客户端接收到返回消息:"+msg); 14 } 15 16 public void sendMsg(final String msg){ 17 System.out.println("客户端开始发送消息"+msg); 18 new Thread(new Runnable() { 19 @Override 20 public void run() { 21 server.receiveMsg(Client.this,msg); 22 } 23 }).start(); 24 25 System.out.println("客户端成功发送消息"+msg); 26 } 27 }
服务端接收消息后使用sleep模拟消息处理耗时,处理完成后回调通知客户端。
1 public class Server { 2 public Server() { 3 } 4 5 public void receiveMsg(CallBack callBack, String msg) { 6 System.out.println("服务器接收到客户端消息:" + msg); 7 try { 8 Thread.sleep(5000); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 String backMsg = "你好,客户端!"; 13 System.out.println("服务器处理成功,返回消息:" + backMsg); 14 callBack.backMsg(backMsg); 15 } 16 }
我们看到java中回调是通过CallBack接口来实现的,(当然也可以不使用接口,但使用接口的优点 就不用多说了吧),Client实现接口并在调用时发送自身引用(Client.this),Server通过引用来调用回调函数。可以写个测试类测试下:
1 public class CallBackTest { 2 public static void main(String[] args) { 3 Client client = new Client(new Server()); 4 client.sendMsg("你好,服务端!"); 5 } 6 }
运行结果:
客户端开始发送消息你好,服务端!
客户端成功发送消息你好,服务端!
服务器接收到客户端消息:你好,服务端!
服务器处理成功,返回消息:你好,客户端!
客户端接收到返回消息:你好,客户端!
我们可以看到通过回调,我们避免了同步阻塞。
下期我们将通过一个故事介绍观察者模式,以及和回调函数之间的关系。