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