I. 简介

Callback 的本质是:让其他人代替自己代替自己完成某件事,并且在事情完成后拿到自己想要的结果.
举个简单的例子,去楼下超市买东西,结果发现想要的某件商品没货了,于是就给店员留下了自己的联系方式,让店员在有货后通知自己.在这里,联系方式就是回调函数,也就是其他人在完成任务后会调用的函数;而留下联系方式的过程则称为登记回调函数,也就是要其他人帮自己去做某件事.

II. 实例

假如现在有一个学生要写作业,其方法定义如下所示.

public class Student{

    public void doHomework(String question, String answer) {
        System.out.println("作业本");
        if (answer != null)
            System.out.println("作业: " + question + " 答案: " + answer);
        else
            System.out.println("作业: " + question + " 答案: (空白)");
    }

    public static void main(String [] args) {
        Student student = new Student();
        student.doHomework("1+1=?", "2");
    }

}

output://
作业本
作业: 1+1=? 答案: 2


但如果此时这名学生不想写作业了,他可以请他的室友完成;此时,他请室友完成这个动作为登记回调函数,而doHomework()这个方法则是回调函数.

public class Roommate {

    public void getAnswer(String question, Student student) {
        if (question.equals("1+1=?"))
            student.doHomework(question, "2");
        else
            student.doHomework(question, "(空白)");
    }

    public static void main(String [] args) {
        Roommate roommate = new Roommate();
        roommate.getAnswer("1+1=?", new Student());
    }
}

output://
作业本
作业: 1+1=? 答案: 2

在这里,Roommate 的getAnswer() 方法接收了问题和学生对象,并在完成问题后回调了学生对象的doHomework()方法. 这便是回调的基本定义.



但是此处有一个问题,即Roommate 的getAnswer()方法中,传入的是学生对象;这样一样,就把学生对象的方法都暴露出来了,getAnswer()方法可以随意对学生对象进行操作。因此,回调函数一般都是传入接口.
同时使用接口的另一个原因是,如果传入的对象不是学生类对象,Roommate 需要重载方法,这无疑会加大工作量。

首先定义一个接口.

public interface Question {
    public void doHomework(String question, String answer);
}

然后让学生类执行此接口.

public class Student implements Question{

    @Override
    public void doHomework(String question, String answer) {
        System.out.println("作业本");
        if (answer != null)
            System.out.println("作业: " + question + 
                                    " 答案: " + answer);
        else
            System.out.println("作业: " + question + " 答案: (空白)");
    }
}

随后,在Roommate 方法中,更改getAnswer()方法.

public class Roomate {

    public void getAnswer(String question, Question someOne) {
        if (question.equals("1+1=?"))
            someOne.doHomework(question, "2");
        else
            someOne.doHomework(question, "(空白)");
    }
}

这样,就不用担心类方法暴露的问题了。



同时还有一点需要注意的是,通常接口会以匿名类的方式实现。

public class Roomate {

    public void getAnswer(String question, Question someOne) {
        if (question.equals("1+1=?"))
            someOne.doHomework(question, "2");
        else
            someOne.doHomework(question, "(空白)");
    }

    public static void main(String [] args) {
        Roomate roomate = new Roomate();
        roomate.getAnswer("1+1=?", new Question() {
            @Override
            public void doHomework(String question, String answer) {
                 System.out.println("作业: " + question + 
                                         "  答案: " + answer);
            }
        });
    }
}

III. 加入线程的实例

Callback 应用较广泛的地方便是线程中.
假设例子仍为上述例子,但这次题目变难,室友需要更多的时间来思考;如果不采用线程,主线程需要一直等待室友,无疑会降低用于体验。

public class Student implements Question{
    public static final String HOMEWORK = "当x趋向于0,sin(x)/x =?";

    @Override
    public void doHomework(String question, String answer) {
        System.out.println("作业本");
        if (answer != null)
            System.out.println("作业: " + question + 
                    " 答案: " + answer);
        else
            System.out.println("作业: " + question + " 答案: (空白)");
    }

    public void ask(String homework, Roomate roommate) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                roommate.getAnswer(homework, Student.this);
            }
        }).start();

        goHome();
    }

    private void goHome() {
        System.out.println("好室友,拜托你了");
    }

    public static void main(String [] args) {
        Student student = new Student();
        student.ask(HOMEWORK, new Roomate());
    }


}

public class Roomate {

    public void getAnswer(String question, Question someOne) {
        if (question.equals("1+1=?"))
            someOne.doHomework(question, "2");
        else if (question.equals(Student.HOMEWORK)) {
            System.out.print("思考中...  ");
            for (int i = 0; i < 3; i++) {
                System.out.print(i + "秒  ");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println();

            someOne.doHomework(question, "1");
        }
        else someOne.doHomework(question, "(空白)");
    }

    public static void main(String [] args) {
        Roomate roomate = new Roomate();
        roomate.getAnswer("1+1=?", new Question() {
            @Override
            public void doHomework(String question, String answer) {
                System.out.println("作业: " + question + 
                                        "  答案: " + answer);
            }
        });
    }
}


output://
好室友,拜托你了
思考中...  0秒  1秒  2秒  
作业本
作业: 当x趋向于0,sin(x)/x =? 答案: 1