您的位置:首页 > 编程语言 > Java开发

一文彻底搞懂Java回调机制

2019-01-05 13:58 1151 查看

转载请注明出处:https://blog.csdn.net/Venhoum/article/details/85805051

相信很多人在进行Java或者Android开发的时候都或多或少遇到过回调函数,它们通常看上去像是CallBack函数族,让基础本来就是不怎么好的同学望而却步。接下来我站在一个菜鸟的角度为受到“Java回调”问题困扰的同学讲解一下我的理解,如有不准确还请大神多多指教。。。

在开始讲解之前,首先来看一个经典例子小明求小红算数,小红开启算数业务挣钱的问题,注意请大家仔细看下这篇博客,我们之后的举得栗子都在这个基础之上。大致意思就是小明(此处为Student类)仅会算10以内的加法,但老师给他出难题,他只能求助小红(此处为SuperCalculator)类,帮助自己计算,并将计算所得结果填空,故事背景简单回顾到此为止。

1.什么是回调

百度百科解释:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
我们也能看到比较简单直接的回答:在面向对象程序中,A类调用B类中的C方法,C方法又去调用A类的D方法,此时这个A类中的D方法就是大名鼎鼎的回调方法。

对应这个场景中,Student就是A类,SuperCalculator类就是B类,这个SuperCalculator中的add方法就是C方法,由以上代码看出add方法中调用了A类中的fillBlank方法。所以fillBlank就是回调(D)方法了。

2.为什么要需要回调

我们看到回调是A类调用B类的C方法,让C方法帮助自己去做一些数据处理,本身这个处理是A类做不了的,而A又需要及时的拿到C方法对自身数据的处理结果,然后再对这个结果进行别的逻辑处理。就像本例中,A类中有两个加数,A类想得到这两个加数相加的结果(result),然后用result填空。下面举例来说明如果不采用回调机制应该怎么操作:

SuperCaculator类中的add方法应该将加和的result返回,然后Student类在调用的时候再定义变量去接收这个返回值,然后拿到这个返回值之后再进行后续逻辑操作。

[code]class SuperCalculator
{
int add(int a,int b){
return a+b;
}
}

Student中的callHelp(求助)方法:

[code]void callHelp (int a, int b)
{
int result= new SuperCalculator().add(a,b);
System.out.println(a+"+"+b+"="+result);
}

我们似乎看不出使用回调机制的优势,当然在这里的result就一个值,可以作为返回值回传出来,那如果同时需要拿到多个值,那显然用这种方法就无法满足了。比如:老师给小明两个数值,并让小明计算以这两个数值为边长的矩形的周长和面积。那老师同时要周长和面积,显然无法用一个返回值来回传结果啊。

此时小明还得去请教年长于他的小红,此时我们为SuperCalculator增加一个calculate方法,具体如下:

[code] /**
* @param a 矩形的长
* @param b 矩形的宽
* @param customer 请求计算的顾客
*/
void calculate(int a,int b,doJob customer)
{
int l=2*(a+b);
int s=a*b;
customer.fillBlank(a,b,l,s);
}

接口修改为:

[code]interface doJob{
    void fillBlank(int a,int b,int l,int s);
}

此时小明(Student)类中再重写fillBlank方法时就可以:

[code]@Override
public void fillBlank(int a,int b,int l,int s){

System.out.println(name+"callHelp for xiaohong caculate :the perimeter of the rectangle with sides "+a+" and "+b+" is " + l);

System.out.println(name+"callHelp for xiaohong caculate :the area of the rectangle with sides "+a+" and "+b+" is " + s);

    }

在实际开发过程中,可能要回传不止两个结果,由此一下将结果回传到回调函数中,这也就是引入回调机制的必要性。

3.为何要借助interface(接口)实现,接口的在其中的必要性?

作为一个菜鸟,有时在本来就不好理解的知识点面前,又碰到了自己的软肋——接口,接下来介绍一下为何要用接口,接口带来的实际好处,不用接口面临什么麻烦。

举个栗子说明,以小明求小红算加法题为例,如果不引入接口大概是这样的:

[code]class SuperCalculator
{
    void add(int a,int b,Student customer)//区别,此时小红的该方法就只能为小明(Student)做计算
    {
        int result=a+b;
        customer.fillBlank(a,b,result);
    }
}

如果老婆婆求小红算账,则小红还得再为老婆婆写一个方法:

[code]void add(int a,int b,Seller seller)//区别,此时小红(SuperCaculator)该方法就只能为老婆婆(Seller)做计算
    {
        int result=a+b;
        seller.fillBlank(a,b,result);
    }

而我们引入接口,谁需要请求小红做计算,则只需要实现小红定义的接口doJob就可以了,减少了代码的重用。

4.举个实际应用中的栗子:

看到这里,想必你肯定对所谓的回调有了一定的认识,那么接下来举个实际Android开发中的栗子,在Android开发过程中,肯定用过Button,在Button中注册监听事件,然后重写onClick方法。最终实现的效果就是点击一下按钮,实现我们自己的想要的逻辑。废话不多说,栗子:

[code]Button pill=(Button)findViewById(R.id.pipi);
pill.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
*逻辑操作
*/
}

这里的pill是我得到的按钮的实例,setOnClickListener(OnClickListener onClickListener)是View类中的一个监听器,注意此处参数为View类中的一个接口。这里之看上去是在setOnClickListener方法中的参数列表里new了一个接口,这么写实际上相当于new了一个实现了OnClickListener接口的匿名内部类,然后在这个匿名内部类中重写接口中声明的onClick(View v)方法。

这时你可能像我刚开始一样有个疑问,谁调用的onClick方法?要想理解这一点还需要对Android的View类有一定的了解,看下源码:

[code]public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}

这个参数OnClickListener l被传给了View类中静态内部类 ListenerInfo中的成员变量mOnClickListener,之后mOnClickListener被View类中的performClick()方法去获取,并用其调用onClick方法。看下performClick()源码:

[code]public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
return true;
}
return false;
}

我们自己不会显式地去调用onClick方法。当用户触发了该按钮的点击事件后,它会由Android系统来自动调用。解释到这里可能越来越复杂,建议去了解一下Android中Button事件再去理解回调这部分内容。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: