回调函数
一.定义
回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
二.步骤:
- 类
A
的a()
方法调用类B
的b()
方法 - 类
B
的b()
方法执行完毕主动调用类A
的callback
方法
在 Java中不存在函数指针,通常通过接口来实现:把实现某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。
三.例子(使用接口实现回调,传递对象也可以)
大概是个模拟快递柜取件运行的网络编程代码,在进行改造的时候遇到socket
通信问题没有解决,就把代码做删减再贴出来了
1. 回调接口
/**
* 回调接口
* @author ShiYu
*
*/
public interface CallBackInterface {
void call();
}
2.使用回调,就用一个类来实现这个回调接口,重写call()
方法,也就是最后实现回调的函数(上图中的B类)
/**
* 实现接口,在这个类的方法中去调用另一个类的方法
* Make a complaints by ShiYu:本来用Runnable接口也挺香的,非要整点炫酷的给自己挖坑 0_o?
* @author ShiYu
*/
public class ClientThreadCallBack implements CallBackInterface {
//此处相当于
ExpressServerCallback expressServerCallback = new ExpressServerCallback();
private Socket socket;
// 构造函数将合传入
public ClientThreadCallBack(Socket socket) {
this.socket = socket;
}
@Override
public void call() {
// TODO Auto-generated method stub
System.out.println("回调");
new Thread(() -> {
// 线程信息
System.out.println("cilent<" + Thread.currentThread().getName() + ">");
System.out.println("\"" + socket.toString() + "\"");
System.out.println("socket Info client port:" + socket.getPort());
try {
while (true) {
System.out.println("输入的取件码:");
String keyPackageNumber = br.readLine();// 阻塞方法
System.out.println(keyPackageNumber);
//调用另一个类的方法,socket是引用类型
ExpressServerCallback.takeOut(socket, keyPackageNumber);
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
3.在这个类(main
)的方法中开启调用
/**
* main方法,使用回调函数
* @author ShiYu
*/
public class ExpressServerCallback {
static ArrayList<Express> expressCabinet = new ArrayList<>();
public static void main(String[] args) {
//从工具类获取server实例,单例模式(懒汉)
ServerSocket serverSocket = SocketUtils.getInctence();
try(serverSocket) {
while (true) {
Socket socket = serverSocket.accept();//阻塞方法
//调用
ClientThreadCallBack cliBack = new ClientThreadCallBack(socket);
cliBack.call();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public synchronized static boolean takeOut(Socket socket, String keyPackageNumber) {
//省略
}
/**
* 发送信息
* @param msg
* @return
*/
public static boolean sendMassage(Socket socket, String msg) {
//省略
}
}
4.说明
这个例子本意是在main
方法中用回调的方法为多个用户开启通信的线程,然后如果客户管发送了数据,就回调main
方法所在的类中对成员变量集合的增删查改,过程:
1.ExpressServerCallback.main -> cliBack.call()
ExpressServerCallback
类中的main
方法,实例化ClientThreadCallBack
类的对象后,调用ClientThreadCallBack.call()
方法
2.ClientThreadCallBack.call() -> ExpressServerCallback.takeOut()
在ClientThreadCallBack
类中的call方法中,经过一个阻塞方法(也可以是其他判断条件),当满足条件之后,回去调用了ExpressServerCallback
中进行集合操作的方法
四.后记
本意是想实现多线程操作同一个集合的时候,避免将集合传过来传过去,然后想到这个好像很好玩的方法,结果socket的地方出现了bug,到现在还没有解决,socket
不受控制的多发送了一次空数据,导致客户端和服务器的流程对接出现了问题
其实说到这个也就提到了 Vector
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
可以看到源码中用了synchronized 内置锁,当然,多线程想要保证线程安全就要牺牲效率