理解Java回调机制

来源:互联网 发布:关系数据库特性 编辑:程序博客网 时间:2024/06/05 17:52

其实对于回调机制,在实际使用中还是经常用到的。但好笑的是,一直没能对所谓的回调的概念有一个很清晰的理解。

最近抽空看一些书的时候,老是时不时的提到回调的概念。那好吧,正好抽空来简单总结总结,加深一下印象和理解~


网上的百科之类的资料中,看到的对于回调比较书面和规范的解释是:

在计算机程序设计中,回调函数是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。


不知道别人对于这样比较“官方的”概念是一种怎么样的感受,反正我是“一头包”,个人十分讨厌这种书面解释。

于是又在网上看了一些别人写的关于回调的文章,发现很久以前就有别人作了一个很形象的比喻:

某天,我打电话向你请教问题,当然是个难题,^_^,你一时想不出解决方法,我又不能拿着电话在那里傻等。

于是我们约定:等你想出办法后打手机通知我,这样,我就挂掉电话办其它事情去了。

过了XX分钟,我的手机响了,你兴高采烈的说问题已经搞定,应该如此这般处理。故事到此结束


这个例子说明了“异步+回调”的编程模式。其中,你后来打手机告诉我结果便是一个“回调”过程;

我的手机号码必须在之前就告诉你,这便是注册回调函数;我的手机号码应该有效并且手机能够接收到你的呼叫,这是回调函数必须符合接口规范。


而该作者针对这一情况给出的例子如下,假设我是程序员A,以下是我的程序a:

public class Caller  {      public MyCallInterface mc;        public void setCallfuc(MyCallInterface mc)      {         this.mc= mc;      }        public void call(){         this.mc.method();      }  }  

我还需要定义一个接口,以便程序员B根据我的定义编写程序实现接口。
    public interface MyCallInterface      {          public void method();            }  

于是,程序员B只需要实现这个接口就能达到回调的目的了:

    public class B implements MyCallInterface      {          public void method()          {             System.out.println("回调");          }                public static void main(String args[])          {             Caller call = new Caller();             call.setCallfuc(new B());             call.call();          }      }  

看完这个例子过后,对于回调似乎有一定的理解了,但似乎却还是处于一种似懂非懂的状态,蛋疼中...

于是又继续查找了一些资料,然后发现,上面的例子对于“回调”思想体现的重点大致在于:

在Java当中,一般来说类的成员变量一般都是数据对象,主要是用来传递数据用的。

而回调的意思是:把一段程序作为成员变量,在特定的场合使用该段程序。这就是回调的核心

这就刚好对应了上面讲到的,网上的百科资料中对于回调比较“书面”和“官方”的解释了。


所以,大致来讲对于上面的例子中:

程序员A就是想要把一段程序,也就是函数“method”作为成员变量来使用;但是呢,Java的语言特性导致了一段程序是不能单独作为变量和参数来使用的。

那么,基于面向对象的特性,就只能将其封装成类也就是对象来使用。但是这段代码在程序员A进行编程的时候又是无法确定的,它只能提供一个入口(即声明),让B去实现。

这种特点恰恰适合于Java当中Interface的特点。于是,函数“method”被封装在了接口“MyCallInterface”当中。


到了现在,可算明白了,“method”方法就是这里的所谓的“回调函数”。

而这个例子中,整个的回调过程,就可以简单的分解为:

程序员A遇到难题,无法解决,于是请教程序员B = ProgrammerA call toProgrammerB

程序员B收到问题,进行思考,最终给出解决方案 = ProgrammerB call toProgrammerA

当程序员给出解决方案,通知给程序员A时,这个过程就是所谓的回调了。


当我对这个例子进行了一番琢磨后,感觉就是:

对于打电话请教问题的这个经典用例自身,是很能够描述回调的思想的。但是,对于这个例子来说,

对于回调的说明似乎并不能得到给人一种醍醐灌顶的体验。


至少我是这样觉得的,所谓回调回调的,通常第一感觉就是:

既然要回,那么就如同打电话请教问题的例子一样,

我打电话给你了,你得回电给我,这才算是完成了一个回调嘛。

但在上面他的例子中,这个“回电”的过程没有一个很明显的体现。


另一种场景似乎更能更加明显直接的对于回调进行说明。

在其它的博客里看了对于回调的经典应用场景有这样一种说法:

  • Class A实现接口CallBack callback——背景1
  • class A中包含一个class B的引用b ——背景2
  • class B有一个参数为callback的方法f(CallBack callback) ——背景3
  • A的对象a调用B的方法 f(CallBack callback) ——A类调用B类的某个方法 C
  • 然后b就可以在f(CallBack callback)方法中调用A的方法 ——B类调用A类的某个方法D

对于这种应用场景,我们来结合一些“身边的实际现象”,就能很好的帮助自身进行理解。

最近恰逢年初,又是一年跳槽的高峰期了。

所以对于员工离职与跳槽的现象,其实也可以帮助我们对“回调”进行理解。


跳槽的很常见的原因自然就是薪资问题,那么:

假设我们作为一个员工,又是新的一年了,工资依旧可怜巴巴,心里捉急啊。

这时怎么办呢?跳吧?那万一刚跳,薪资又要调整了呢?

于是,决定了,先和BOSS“谈判”,根据结果再做最后的决定。

这个时候,实际上也就是所谓的一种回调。

我们以一个程序猿的角度来分解这个问题:


首先,员工想要从老板处得知关于薪资调整的结果,那么就需要一个“途径”,于是出现了一个回调接口:

/** * 回调接口 * @author hql  * 2015/03/03 */public interface Negotiation {/* * 实际这就是你向BOSS提供的回复你调薪询问的一种途径 *  BOSS根据该途径给你一个结果,而你就可以通过该结果来做出决定 */public void setNewSalary(int salary);}

接下来,就是员工类的定义:
/** * 员工类 * @author hql  * 2015/03/03 */public class Employee implements Negotiation {//背景1private Boss boss = new Boss(); //背景2private int newSalary;/* * 开启谈判 */void startNegotiation() {new Thread(new Runnable() {@Overridepublic void run() {//背景:Class A调用Class B中的某个方法Cboss.doNegotiation(Employee.this);}}).start();}/* * 回调函数,从BOSS处获取薪资调整结果 */@Overridepublic void setNewSalary(int salary) {this.newSalary = salary;makeDecision();}/* * 根据薪资调整结果做出最终决定 */void makeDecision() {if (newSalary >= 10000) {System.out.println("调薪满意,继续奋斗!");} else {System.out.println("呵呵,拜拜了您!");}}}

紧接着,是老板类:
/** * 老板类 * @author hql  * 2015/03/03 */public class Boss {public void doNegotiation(Negotiation negotiation) {//背景3System.out.println("小子去年干的不错,涨!");// Boss call back to Employee - 回调negotiation.setNewSalary(10000);}}

一切准备工作就绪,接下来就是真正的“谈判了”:
/** * 回调函数测试类 * @author hql  * 2015/03/03 */public class TestCallBackFunc {public static void main(String[] args) {Employee xiaoli = new Employee();xiaoli.startNegotiation();}}

到了这里,终于对所谓的“回调机制”有了一个较为清晰的理解了。
正如上面的例子当中,所谓回调的过程就是指:

首先,是员工类(Class A)当中调用了老板类(Class B)的方法:“doNegotiation”,由此向老板提出调薪的要求。(employee call to boss)

然后,老板收到请求,做出决定。于是老板类(Class B)回过头又调用了员工类(Class A)当中的方法:“setNewSalary”。(boss call to employee)


所以,其实针对回调,如果我们作更通俗的理解,其实就是:

一个员工想要调薪,针对于调薪,就自然会有一个结果,但是这个结果步是员工自身能决定的,需要由BOSS来拍板。

那么,自然的,针对于该调薪结果,就需要一个“介质”,也可以说是一个“渠道”来在员工与BOSS之间进行沟通。

而“回调函数”在整个回调过程中,起到的作用就是这个“介质”。


我们不妨把上面的例子当中的回调函数“setNewSalary”视作一张“员工薪资调整申请表”,那么整个回调过程就变成了:

某员工想要申请薪资调整,于是找到老板(调用BOSS类当中的方法),告诉BOSS,我想要调薪了。

老板收到了请求,做出了决定。现在要将这个结果返回该员工,这个返回的过程就被叫做回调。

但这个返回结果的方式肯定应该符合一定的规范,因为至少要让该员工意识到,BOSS已经针对于你的要求给出结果了。

“员工薪资调整申请表”就是该公司针对于“调薪结果”的提出的规范,员工根据该申请表就能得知自己的调薪结果。

所以说,之所以我们在程序中定义回调接口,其实就是在声明一种规范,确保程序的严谨性。


试想一下,该老板给出结果过后,正好碰到某个保洁人员,于是对这个清洁人员,正好,你去告诉某某员工他的调薪结果是xxxxx,那合理吗?


3 0
原创粉丝点击