Java 回调函数
前言
最近在复习一些编程的基础知识,发现回调函数这个东西用起来很方便。
于是就研究了一下Java是如何实现真正意义上的回调函数。
直接调用与间接调用
在理解回调函数之前,我们需要先来理解在 c/c++ 中,什么是直接调用,什么是间接调用。
直接调用
在函数A的函数体里,通过书写函数B的函数名来调用之,使内存中对应函数B的代码得以执行。
这里,A称为“主叫函数”(Caller),B称为“被叫函数”(Callee)。
间接调用
在函数A的函数体里并不出现函数B的函数名,而是通过指向函数B的函数指针p,来使内存中属于函数B的代码片断得以执行。
两者比较而言,间接调用的灵活性更强,因为传入的参数只是一个函数的指针,所以我们可以在函数中随意改变指针指向的地址,从而调用不同的函数。
Java的回调函数
在 Java 中,回调函数是指:
A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法。我们也可以实现直接调用和间接调用。
为了更好的理解,借用一位博主的例子,来向进行说明。
比如,现在许多人遇到计算问题都会借助计算器。那我们就模拟一个场景,现在有一个计算器可以处理加法运算。学生群体和销售员群体,都需要借助这个计算器进行计算,并进行写结果的操作,在计算器计算期间,学生和销售员都会进行休息(多线程异步计算)。
直接调用
Java 中的直接调用是,直接通过函数入参的对象引用来调用函数。并且,我们可以通过继承的方式,来进行一个优化的写法。
父类:Idiot
public class Idiot {
//求助计算器算数
public void callHelp(final int a, final int b) {
//异步计算
new Thread(new Runnable() {
public void run() {
new SuperCalculator().add(a, b, Idiot.this);
}
}).start();
System.out.println("Idiot 在休息……");
}
//写的操作
public void fillBlank(int result) {
System.out.println("idiot:" + result);
}
}
子类1:Student
public class Student extends Idiot {
@Override
public void callHelp(final int a, final int b) {
//异步计算
new Thread(new Runnable() {
public void run() {
new SuperCalculator().add(a, b, Student.this);
}
}).start();
System.out.println("student 在休息……");
}
@Override
public void fillBlank(int result) {
System.out.println("student:" + result);
}
}
子类2:Seller
public class Seller extends Idiot {
@Override
public void callHelp(final int a, final int b) {
//异步计算
new Thread(new Runnable() {
public void run() {
new SuperCalculator().add(a, b, Seller.this);
}
}).start();
System.out.println("seller 在休息……");
}
@Override
public void fillBlank(int result) {
System.out.println("seller:" + result);
}
}
计算器
public class SuperCalculator {
public void add(int a, int b, Idiot idiot) {
//回调函数(写操作),通过父类进行调用。
idiot.fillBlank(a + b);
}
}
测试类
public class Test {
public static void main(String[] args) {
Student student = new Student();
Seller seller = new Seller();
student.callHelp(10,10);
seller.callHelp(20,20);
}
}
在这个直接回调的写法中,我们只需要用使用者的视角专注业务类型,而不需要关心中间做了什么。也就是说在测试的过程中,我们并没有 “写操作” 的痕迹,这一过程在计算器中被回调。这是一个非常方便的写法。
间接调用
Java 中的间接调用是使用接口来完成的。在这里,我们将 “写操作” 这一行为抽象为一接口,那么计算器就不需要关心是被哪个群体使用的,而更加关心业务本身,让代码解耦。
DoJod接口
public interface DoJob {
void fillBlank(int a, int b, int result);
}
Student
public class Student {
public class doHomeWork implements DoJob {
public void fillBlank(int a, int b, int result) {
System.out.println("student 计算结果:" + result);
}
}
public void callHelp(final int a, final int b) {
new Thread(new Runnable() {
public void run() {
//调用superCalculator的函数
new SuperCalculator().add(a, b, new Student.doHomeWork());
}
}).start();
System.out.println("student 在休息……");
}
}
Seller
public class Seller {
public class doCalculate implements DoJob {
public void fillBlank(int a, int b, int result) {
System.out.println("seller 计算结果:" + result);
}
}
public void callHelp(final int a, final int b) {
new Thread(new Runnable() {
public void run() {
//调用superCalculator的函数
new SuperCalculator().add(a, b, new doCalculate());
}
}).start();
System.out.println("seller 在休息……");
}
}
计算器
public class SuperCalculator {
//屏蔽了调用的具体对象,只对业务接口负责。
public void add(int a, int b, DoJob job) {
//回调函数(写操作),通过接口进行调用。
job.fillBlank(a, b, a + b);
}
}
测试类
public class Test {
public static void main(String[] args) {
Student student = new Student();
Seller seller = new Seller();
student.callHelp(10, 10);
seller.callHelp(20, 20);
}
}
两者对比
其实两者谁优谁劣不好评判,两者的不同主要在于:
直接回调通过父类对象的引用调用函数,利用 Java 向上转型(**继承**)的特性。
而间接回调通过接口进行函数的调用,利用 Java 动态绑定(**多态**)的特性。
可以针对不同的业务场景,使用不同的代码结构。
借用上方例子,如果业务更专注于人群的 “操作”,那么建议使用间接回调;如果业务更专注于人群类别的辨析,那么建议使用直接回调。
总结
说到底,回调函数到底有什么用?
注意到之前,所有的计算都是使用的多线程异步计算,也就是说,我们不需要去监听计算器什么时候得出结果,而只需要在计算完成时,对群体的函数进行回调就行了,感觉是不是很方便哈哈。
参考博客
https://www.cnblogs.com/winifredaf/p/10016871.html
https://blog.csdn.net/lc545126483/article/details/79954612