详解Java的回调机制
作者:Q-WHai 发布时间:2023-07-27 07:17:43
模块之间总是存在这一定的接口,从调用方式上看,可以分为三类:同步调用、回调和异步调用。下面着重详解回调机制。
1. 概述
Java 中的回调机制是一个比较常见的机制,只是有可能在你的程序中使用得比较少,在一些大型的框架中回调机制随处可见。本文就通过一些具体的实例,慢慢走近 Java 的回调机制。
2.回调
所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法。实际在使用的时候,也会有不同的回调形式,比如下面的这几种。
2.1 同步回调
这里我假设这样的一种情况。
A 公司的总监 B 跟他的下属(项目经理 C)说要做一个调研,不过不用 C 自己亲力亲为。可以让经理 C 去安排他下面的程序员 D 去完成。经理 C 找到了程序员 D,并告诉他,现在要完成一个调研任务。并且把调研的结果告诉经理 C。如果有问题,还是要继续的。 因为这里是 C 让 D 去做一件事情,之后 D 还是要将结果与 C 进行沟通。这样就是回调的模型了。下面是一般回调的类图:
首先我们要有一个回调的接口 CallbackInterface
CallbackInterface.java
public interface CallbackInterface {
public boolean check(int result);
}
背景里,程序员 D 是要将结果与项目经理 C 进行沟通的,所以这里项目经理需要实现上面的回调接口:
Manager.java
public class Manager implements CallbackInterface {
private Programmer programmer = null;
public Manager(Programmer _programmer) {
this.programmer = _programmer;
}
/**
* 用于 Boss 下达的委托
*/
public void entrust() {
arrange();
}
// 进行安排下属进行 study 工作
private void arrange() {
System.out.println("Manager 正在为 Programmer 安排工作");
programmer.study(Manager.this);
System.out.println("为 Programmer 安排工作已经完成,Manager 做其他的事情去了。");
}
@Override
public boolean check(int result) {
if (result == 5) {
return true;
}
return false;
}
}
对于程序员 D 来说他需要持有一个经理 C 的引用,以便与他沟通。不过,这里是总监 B 让 经理 C 去安排的任务。也就是说这里也可以让其他的经理,比如说经理 B1, B2等等。因为经理都实现了回调的接口,所以这里就可以直接让程序员 D 持有这个接口就可以了。如下:
Programmer.java
public class Programmer {
public void study(CallbackInterface callback) {
int result = 0;
do {
result++;
System.out.println("第 " + result + " 次研究的结果");
} while (!callback.check(result));
System.out.println("调研任务结束");
}
}
对于总监来说就更简单明了了,因为这相当于一个 Client 测试:
Boss.java
public class Boss {
public static void main(String[] args) {
Manager manager = new Manager(new Programmer());
manager.entrust();
}
}
运行结果:
Manager 正在为 Programmer 安排工作
第 1 次研究的结果
第 2 次研究的结果
第 3 次研究的结果
第 4 次研究的结果
第 5 次研究的结果
调研任务结束
为 Programmer 安排工作已经完成,Manager 做其他的事情去了。
2.2 异步回调
还是上面的例子,你的项目经理不可能要一直等你调研的结果。而是把这个任务交给你之后,他就不管了,他做他的,你做你的。所以,这里需要对回调的函数进行异步处理。
所以,这里我们需要修改 Programmer 类的代码,修改如下:
Programmer.java
public class Programmer {
public Programmer() {
}
public void study(CallbackInterface callback) {
new StudyThread(callback).start();
}
// --------------------------- Programmer 正在做的工作 ---------------------------
class StudyThread extends Thread {
CallbackInterface callback = null;
public StudyThread(CallbackInterface _callback) {
callback = _callback;
}
@Override
public void run() {
int result = 0;
do {
result++;
System.out.println("第 " + result + " 次研究的结果");
} while (!callback.check(result));
System.out.println("调研任务结束");
}
}
}
运行结果:
Manager 正在为 Programmer 安排工作
为 Programmer 安排工作已经完成,Manager 做其他的事情去了。
第 1 次研究的结果
第 2 次研究的结果
第 3 次研究的结果
第 4 次研究的结果
第 5 次研究的结果
调研任务结束
2.3 闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
2.3.1 普通调用
首先,我们可以看看在正常情况下的调用是怎么进行的。
Incrementable.java
interface Incrementable {
void increment();
}
这是一个普通的接口(在普通调用里只是普通接口,在回调中就是回调接口,这一点应该很好理解吧)。
Callee1.java
class Callee1 implements Incrementable {
private int i = 0;
@Override
public void increment() {
i++;
System.out.println(i);
}
}
Callbacks.java
public class Callbacks {
public static void main(String[] args) {
Callee1 callee1 = new Callee1();
callee1.increment();
}
}
Callbacks 是一个测试客户端类,没啥好说的,直接看上面的代码。
2.3.2 回调初试
上面的普通调用也没啥好说的,因为这对于一个正常的 Java 程序员来说都应该是想都不用想就可以搞定的事情。
现在如果要构成回调,那么对于程序的结构或是逻辑的思维上都不可能只有一个被调用者(被回调的对象 Callee1),还需要一个调用者对象。调用者可以像下面这样来编写:
Caller.java
class Caller {
private Incrementable callbackReference;
public Caller(Incrementable _callbackReference) {
callbackReference = _callbackReference;
}
void go() {
callbackReference.increment();
}
}
这里 Caller 持有一个回调接口的引用 callbackReference,就像在上面说到的程序员需要持有一个项目经理的引用,这样就可以通过这个引用来与项目经理沟通。这里的 callbackReference 也正是起到了这个作用。
现在我们来看看测试类的编写:
Callbacks.java
public class Callbacks {
public static void main(String[] args) {
Callee1 callee1 = new Callee1();
Caller caller1 = new Caller(callee1);
caller1.go();
}
}
对于到目前为止的程序代码,完全可以对比上面项目经理安排程序员调研技术难题的代码。有异曲同工之妙。
2.3.3 闭包回调
相比于正常的回调,闭包回调的核心自然是在于闭包,也就是对作用域的控制。
现在假设有一个用户(其他程序员)自定义了一个 MyInCrement 类,同时包含了一个 increment 的方法。如下:
class MyInCrement {
public void increment() {
System.out.println("MyCrement.increment");
}
static void f(MyInCrement increment) {
increment.increment();
}
}
另外有一个类 Callee2 继承自上面这个类:
class Callee2 extends MyInCrement {
private int i = 0;
public void increment() {
super.increment();
i++;
System.out.println(i);
}
}
显而易见这里如果要调用 increment() 方法,就变成了一般的函数调用了。所以这里我们需要修改上面的 Callee2 类,修改的目标就是让 Callee2 类可以兼容 MyInCrement 类的 increment() 方法和 Incrementable 的 increment() 方法。修改后:
class Callee2 extends MyInCrement {
private int i = 0;
public void increment() {
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable {
@Override
public void increment() {
Callee2.this.increment();
}
}
Incrementable getCallbackReference() {
return new Closure();
}
}
注意,这里的 Closure 类是一个私有的类,这是一个闭包的要素。因为 Closure 类是私有的,那么就要有一个对外开放的接口,用来对 Closure 对象的操作,这里就是上面的 getCallbackReference() 方法。 Caller 类则没有改变。
对于测试客户端就直接看代码吧:
public class Callbacks {
public static void main(String[] args) {
Callee2 callee2 = new Callee2();
Caller caller2 = new Caller(callee2.getCallbackReference());
caller2.go();
}
}
猜你喜欢
- java 反射机制:测试实体类以Human为例/** * Project: Day12_for_lxy * Created: Lulu *
- 前言各位小伙伴大家好,我是A哥。IDEA上个较大版本的发布,要追溯到4月份了:时隔近4个月,北京时间2020-07-28深夜,Intelli
- maven3 安装:安装 Maven 之前要求先确定你的 JDK 已经安装配置完成。Maven是 Apache 下的一个项目,目前最新版本是
- 介绍跨域CORS,全称是"跨域资源共享"(Cross-origin resource sharing)当页面发出跨域请求
- SpringMVC接收到请求和数据后,进行一些了的处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎
- 本文实例为大家分享了Java打印指定年月日历的具体代码,供大家参考,具体内容如下日历如下:程序如下://打印指定年月的日历public cl
- 一、封装一个工具类1、简易版package net.aexit.construct.acceptance.websky.utils;impo
- 前言现在开发大部分都是服务化或者微服务,数据交换都是跨服务的,这里记录java调取其他接口的方法,下面话不多说了,来一起看看详细的介绍吧。j
- 一、项目要求实现一个通讯录通讯录可以用来存储100个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址提供方法:添加联系人信息删除指定
- 1、通过查找API文档:2、Map.Entry是一个接口,所以不能直接实例化。3、Map.entrySet( )返回的是一个collecti
- 前言周六在公司写Reactor模型,一女同事问我为啥都2023年了还在学习Reactor模型呀,我问她为啥快30的年纪了,周六还在公司看我写
- 本文实例为大家分享了OpenCV实现人脸识别程序的具体代码,供大家参考,具体内容如下//Haar特征检测,人脸识别算法,是用xml作为训练后
- 本文实例讲述了C#启动进程的几种常用方法。分享给大家供大家参考。具体如下:1.启动子进程,不等待子进程结束private void simp
- ForkJoinTask就是ForkJoinPool里面的每一个任务。他主要有两个子类:RecursiveAction和RecursiveT
- 一、导入和导出导入:通过解析excel表格中的数据,然后将数据放到一个集合中,接着通过对持久层操作,将数据插入到数据库中,再加载一下页面,从
- 使用场景EntityListeners在jpa中使用,如果你是mybatis是不可以用的它的意义对实体属性变化的跟踪,它提供了保存前,保存后
- Statement 和 PreparedStatement之间的关系和区别. 关系:Prepa
- java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径package com.liuxing.test;import java.
- 前言在一个 Web 请求中,参数我们无非就是放在地址栏或者请求体中,个别请求可能放在请求头中。放在地址栏中,我们可以通过如下方式获取参数:S
- C++11 引入一个全新的线程库,包含启动和管理线程的工具,提供了同步(互斥、锁和原子变量)的方法,我将试图为你介绍这个全新的线