Java回调函数

Java回调函数

什么是回调函数?

在英语中,callback是回电话的意思,而在开发中callback叫回调函数,其实就是回电话的意思,callback这一个单词已经把“回调”的神表达出来了,作为开发者,完全可以按回电话的场景来理解回调函数。

仔细咂摸一下,回电话这个场景与回调函数的使用场景何其像啊!点击一个按钮或链接,然后等待数据的返回和界面的刷新,具体等待多久也不确定,可能0.1秒就响应了,也可能5秒之后才响应,总之,响应时间是不确定的,不能让人一直等着,这不就像等一个回电话的过程吗?

下面用实际代码模拟上面的沟通过程

这个过程涉及到我和朋友两个实体类,Me类代表我,Friend类代表我的朋友。

因为我要打电话给朋友和接朋友电话通知,所以Me类有三个功能,也叫行为:给朋友打电话,我们用callFriend(Friend)来表示;接到朋友的通知,我们用noticeMe()函数来表示;在给朋友打电话和接朋友电话通知之间的这一段时间,我在忙其它的事情,我们用doOtherthing()函数来表示。

朋友要先给其他小伙伴约时间然后给我打电话,所以Friend类也有个函数order()定义他和其它小伙伴的预约过程,约好时间之后又要给我回电话,所以还要有个参数为Me的对象。

代码如下,这些代码是可以直接运行的,并且用数字对程序的运行顺序做了标记。

Me.java的代码

/**
 * @author lannd
 */
public class Me {
    /**
     * 给朋友打电话
     * @param friend
     * @throws InterruptedException
     */
    public void callFriend(Friend friend) throws InterruptedException {
        System.out.println("1、我打电话给朋友,让他去约时间");
        friend.order(this);
        doOtherThing();
    }
    /**
     * 通知我
     */
    public void noticeMe() {
        System.out.println("6、我收到了朋友的通知");
    }
    /**
     * 忙别的事情去了
     */
    public void doOtherThing() {
        System.out.println("3. 我去忙其它的事情...");
    }
}

Friend.java的代码

import static java.lang.Thread.sleep;
/**
 * @author lannd
 */
public class Friend {
    /**
     * 和其他小伙伴约好时间后通知我
     * @param me
     * @throws InterruptedException
     */
    public void order(final Me me) throws InterruptedException {
        System.out.println("2. 朋友接到电话说:我现在就和其他小伙伴约时间,请稍等...");

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("4. 朋友正在电话联系中...");
                    sleep(5000);
                    System.out.println("5. 朋友约好了,准备给我回电话");
                    me.noticeMe();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

对这段代码加以说明:

  • 函数中使用线程是为了模拟朋友的沟通过程,在实际应用中多为耗时的操作。
  • “朋友”沟通过程与“我”忙其它事情是并行的,“我”并没有等待他。

最后,写主程序

/**
 * @author lannd
 */
public class Main {
    public static void main(String[] args) throws InterruptedException {
        // 创建【我和朋友】对象
        Me me = new Me();
        Friend friend = new Friend();

        // 给朋友打电话
        me.callFriend(friend);
    }
}

看运行结果

从结果中可以看出,我联系朋友之后就忙其它事情了,朋友接到电话之后就去沟通,我也不知道他会沟通多久,朋友在沟通完之后通知我,就完事了。

从主程序可以看出,只有我调用了callFriend(),朋友怎么沟通、怎么回电话,我都不关注,他只需要持有“我”对象的引用就行了,需要通知我时,就通过引用调用noticeMe()

有主动调用过程,也有callBack的过程,这个实例已经展示了回调的全部了,但显然并不完美

试想以下场景:

  • 老板安排给员工一个任务,要求完成工作后通知老板
  • 父母安排你飞机落地后,给他们报个平安。

生活中这样的场景非常多,在实际项目中也是,但是在项目中,我们在每个类中定义一个类似noticeMe()这样的类就不高明了,因为违背了“复用”的编程原则,虽然实现了回调的功能,但并不是真正意义上的回调函数。

所以我们可以对“通知”这一行为进一步抽象,对行为抽象当然要使用接口。

具体代码如下:

/**
 * 定义接收通知接口,所有参与者可实现此接口以接收通知
 * @author lannd
 */
public interface CallBack {
    /**
     * 参与者可以实现接收通知的细节
     * @param friendName
     */
    public void receiveNotice(String friendName);
}
 }

这里有个参数friendName,朋友通知我的时候,可以通过这个参数传递一些信息,比如他的名字。

Me.java实现这个接口后的代码

/**
 * @author lannd
 */
public class Me implements CallBack {
    /**
     * 给朋友打电话
     * @param friend
     * @throws InterruptedException
     */
    public void callFriend(Friend friend) throws InterruptedException {
        System.out.println("1、我打电话给朋友,让他去约时间");
        friend.order(this);
        doOtherThing();
    }
    /**
     * 忙别的事情去了
     */
    public void doOtherThing() {
        System.out.println("3. 我去忙其它的事情...");
    }

    /**
     * 参与者可以实现接收通知的细节
     *
     * @param friendName
     */
    @Override
    public void receiveNotice(String friendName) {
        System.out.println("6、我收到了朋友 -"+friendName+ "- 的通知");
    }
}

Friend.java做相应的修改后:

import static java.lang.Thread.sleep;
/**
 * @author lannd
 */
public class Friend {
    /**
     * 和其他小伙伴约好时间后通知我
     * @param callBack
     * @throws InterruptedException
     */
    public void order(final CallBack callBack) throws InterruptedException {
        System.out.println("2. 朋友接到电话说:我现在就和其他小伙伴约时间,请稍等...");

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("4. 朋友正在电话联系中...");
                    sleep(5000);
                    System.out.println("5. 朋友约好了,准备给我回电话");
                    callBack.receiveNotice("张三");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

Main.java的代码不需要调整。

运行结果和前面基本是一致的,我只是增加了一个参数,大家也可以体会一个这个参数带来的便利。

多了一个接口的定义,代码好像更多了。但在实际扩展当中却更方便了,比如说,老板要接收员工的通知,老板类实现这个接口就完事了;父母要等待孩子的通知,父母类实现这个接口就可以。换句话说,所有要扩展接收通知的类,只要实现这个接口就具备了接收通知的功能。

这样的好处是代码得到了复用,极大的方便了扩展。

通过接口来实现回调的功能,这才是真正意义上的回调函数。


本文转载自:

如何理解java的回调函数? - 知乎 (zhihu.com)