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."));

类图 

适用场景

回调模式适用于以下场景:

  1. 异步操作:当需要在异步操作完成后执行某些操作时,可以使用回调模式。例如,在网络请求中,可以传递一个回调函数,在请求完成后调用该函数处理响应数据。
  2. 事件处理:当需要对事件进行响应和处理时,可以使用回调模式。例如,在图形界面开发中,可以注册某个控件的回调函数,以便在用户触发事件时执行相应的操作。
  3. 插件扩展:当需要为应用程序提供扩展性,允许第三方插件在特定事件发生时进行自定义操作时,可以使用回调模式。例如,游戏引擎中的事件系统允许开发者注册回调函数以响应游戏中的特定事件。
  4. 回调链:当需要按特定顺序执行多个回调函数,并将前一个回调函数的结果传递给下一个回调函数时,可以使用回调模式。这种情况下,回调函数形成了一个回调链。
  5. 模板方法模式:回调模式常与模板方法模式结合使用。模板方法模式定义了一个算法的骨架,而具体的步骤由子类实现。可以使用回调模式将子类中的具体步骤作为回调函数传递给模板方法。

总的来说,回调模式适用于需要在特定事件发生后执行某些操作的情况,以及需要实现解耦和灵活性的场景。它提供了一种在代码间通信的方式,使得代码可以更加模块化和可复用。

Java例子

  • CyclicBarrier 构造函数可以接受回调,该回调将在每次障碍被触发时触发。

FAQ

回调模式如何实现解耦和灵活性?

回调模式通过将一个可执行的代码块(回调函数)作为参数传递给其他代码,实现了解耦和灵活性。

  • 解耦性:回调模式可以将调用方与被调用方解耦,使它们之间的关系更加松散。调用方只需要知道回调函数的接口,而不需要了解具体的实现细节。被调用方在特定的时机调用回调函数,而不需要知道调用方的具体实现。这种解耦性使得系统中的不同部分可以独立地进行修改和扩展,而不会对彼此产生过多的依赖。
  • 灵活性:回调模式提供了一种灵活的扩展机制。通过传递不同的回调函数,可以改变程序的行为或逻辑,而不需要修改原有的代码。这种灵活性使得系统可以适应不同的需求和变化,而不需要进行大规模的修改或重构。同时,回调模式也允许在运行时动态地修改回调函数,从而实现更高级的动态行为。

通过使用回调模式,系统的不同部分可以相互独立地演化和扩展,而不会引入过多的紧耦合关系。这使得代码更加模块化、可复用和可维护。此外,回调模式还可以提高代码的可测试性,因为可以使用模拟或替代的回调函数来进行单元测试。

总而言之,回调模式通过解耦和灵活性的特性,帮助提高了代码的可维护性、可扩展性和可测试性,使系统更加灵活和适应变化。

回调模式和事件驱动模式有什么区别?

回调模式和事件驱动模式是两种常见的设计模式,它们在某些方面有相似之处,但也存在一些区别。

回调模式:

  • 在回调模式中,一个可执行的代码块(回调函数)被传递给其他代码,以便在特定事件发生时被调用。
  • 回调函数通常由调用方提供,用于定义在特定事件发生时应该执行的操作。
  • 回调模式用于实现解耦和灵活性,允许不同模块之间通过回调函数进行通信,但不依赖于具体的实现细节。

事件驱动模式:

  • 事件驱动模式是一种编程范式,其中系统的行为和控制是由事件的发生和处理驱动的。
  • 在事件驱动模式中,组件(如控件、对象等)可以产生事件,并将其发送到事件处理程序进行处理。
  • 事件处理程序是事先定义好的,用于响应特定类型的事件。
  • 事件驱动模式通常涉及事件的发布、订阅和分发机制,以便将事件路由到正确的处理程序。

区别:

  1. 角色和通信方式:在回调模式中,回调函数是被调用方提供给调用方的,通过函数参数进行传递。而在事件驱动模式中,组件产生事件并将其发送给事件处理程序进行处理。
  2. 控制流:在回调模式中,调用方主动调用回调函数来传递控制权,以响应特定事件。而在事件驱动模式中,控制流是由事件的发生和处理驱动的,事件处理程序被动地等待事件的发生。
  3. 灵活性和扩展性:回调模式更加灵活,因为可以将不同的回调函数传递给相同的调用方,从而改变其行为。而事件驱动模式更加适用于大型系统,因为可以通过添加、移除或替换事件处理程序来扩展系统的功能。
  4. 通信机制:回调模式通常使用函数参数进行通信,而事件驱动模式通常使用发布-订阅或观察者模式来实现事件的传递和处理。

需要注意的是,回调模式和事件驱动模式并不是互斥的,它们可以同时存在于一个系统中,相互配合使用来实现不同的需求。

回调模式和观察者模式有什么区别?

回调模式和观察者模式是两种常见的设计模式,它们在某些方面有相似之处,但也存在一些区别。

回调模式:

  • 在回调模式中,一个可执行的代码块(回调函数)被传递给其他代码,以便在特定事件发生时被调用。
  • 回调函数通常由调用方提供,用于定义在特定事件发生时应该执行的操作。
  • 回调模式用于实现解耦和灵活性,允许不同模块之间通过回调函数进行通信,但不依赖于具体的实现细节。

观察者模式:

  • 观察者模式是一种发布-订阅模式,用于在对象之间建立一对多的依赖关系。当一个对象的状态发生变化时,它会通知所有依赖于它的观察者对象。
  • 观察者模式通常由一个主题(被观察者)和多个观察者组成。主题维护观察者列表,并在状态变化时通知观察者。
  • 观察者模式用于实现对象之间的松耦合,使得主题和观察者可以独立变化,而不会相互影响。

区别:

  1. 角色和通信方式:在回调模式中,回调函数是被调用方提供给调用方的,通过函数参数进行传递。而在观察者模式中,主题通常维护观察者列表,并通过通知方法将状态变化信息传递给观察者。
  2. 控制流:在回调模式中,调用方主动调用回调函数来传递控制权,以响应特定事件。而在观察者模式中,主题对象在状态变化时被动地通知观察者,并由观察者决定如何处理通知。
  3. 关注点:回调模式更关注于事件发生后的回调操作。观察者模式更关注于主题和观察者之间的状态变化通知和处理。
  4. 依赖关系:在回调模式中,调用方和被调用方之间存在直接依赖关系,因为回调函数是由调用方提供的。而在观察者模式中,主题和观察者之间松耦合,它们只通过接口进行通信,不直接依赖于具体的实现。

需要注意的是,回调模式和观察者模式可以根据具体的应用场景进行选择和组合使用。在某些情况下,它们可以互为补充,实现更灵活和可扩展的系统设计。

使用回调模式,会存在内存泄露吗?

在Java中使用回调模式时,也存在潜在的内存泄漏问题。内存泄漏可能发生在以下情况下:

  1. 长期持有回调对象:如果一个对象持有一个回调对象的引用,并且该回调对象的生命周期比持有对象更长,那么即使持有对象不再使用,回调对象仍然保持对其的引用,从而导致内存泄漏。
  2. 匿名内部类回调:当使用匿名内部类作为回调对象时,如果匿名内部类引用了外部类的实例,且该实例的生命周期比回调对象更长,那么即使外部类实例不再需要,回调对象仍然保持对其的引用,导致内存泄漏。

使用回调模式,如何避免内存泄露?

以下是一些常见的方法来避免内存泄漏:

  • 及时释放对象引用:确保在不再需要对象时,显式地将其引用设置为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,以允许垃圾回收器回收内存。

      使用静态内部类可以有效地避免内存泄漏问题,因为它们不会持有对外部类实例的引用,从而使得外部类实例可以在不再需要时被垃圾回收。这使得静态内部类成为一种常见的处理回调或复杂逻辑的有效方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/105300.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu - 安装Docker

在Ubuntu上安装Docker分为以下几个步骤&#xff1a; 更新包列表&#xff1a; sudo apt update 安装依赖包&#xff0c;以便允许apt使用HTTPS&#xff1a; sudo apt install apt-transport-https ca-certificates curl software-properties-common 添加Docker官方GPG密钥&a…

Mybatis用Byte[]存图片,前端显示图片

前端页面 static下 也就是说byte[] 转成JSON字符串后,和用BASE64编码后是一摸一样的,那么SpringBoot会自动将实体类转JSON字符串,也就是说根本不需要Base64编码 注意:两个值并非一摸一样,一个多了个双引号 byte[]的值前后有个双引号 有一点点区别 一个有双引号,一个没有…

200、使用默认 Exchange 实现 P2P 消息 之 消息生产者(发送消息) 和 消息消费者(消费消息)

RabbitMQ 工作机制图&#xff1a; Connection&#xff1a; 代表客户端&#xff08;包括消息生产者和消费者&#xff09;与RabbitMQ之间的连接。 Channel&#xff1a; 连接内部的Channel。channel&#xff1a;通道 Exchange&#xff1a; 充当消息交换机的组件。 Queue&#xff…

UGUI交互组件ScrollView

一.ScrollView的结构 对象说明Scroll View挂有Scroll Rect组件的主体对象Viewport滚动显示区域&#xff0c;有Image和mask组件Content显示内容的父节点&#xff0c;只有个Rect Transform组件Scrollbar Horizontal水平滚动条Scrollbar Vertical垂直滚动条 二.Scroll Rect组件的属…

c# xml 参数读取读取的简单使用

完整使用之测试参数的读取&#xff08;xml&#xff09; 保存一个xml文档&#xff08;如果没有就会生成一个默认的 里面的参数用的是我们默认设置的&#xff09;&#xff0c;之后每次更改里面的某项&#xff0c;然后保存 类似于重新刷新一遍。 这里所用的xml测试参数前面需要加…

【TA 挖坑04】薄膜干涉 镭射材质 matcap

镭射材质&#xff0c;相对物理的实现&#xff1f; 万物皆可镭射&#xff0c;个性吸睛的材质渲染技术 - 知乎 (zhihu.com) 薄膜干涉材质&#xff0c;matcap更trick的方法&#xff1f;matcapremap&#xff0c; MatCap原理介绍及应用 - 知乎 (zhihu.com) 庄懂的某节课也做了mat…

echarts折线图(其他图也是一样)设置tooltip自动滚动

按顺序自动滚动效果 <div class"leftComp-charts" id"chartsBox"></div>chartsData: {roadNorm: [],time: []},eChartsTimer: nullinitChartsBox() {this.option {tooltip: {trigger: "axis",axisPointer: {// 方法一type: "s…

数据结构--》掌握数据结构中的查找算法

当你需要从大量数据中查找某个元素时&#xff0c;查找算法就变得非常重要。 无论你是初学者还是进阶者&#xff0c;本文将为你提供简单易懂、实用可行的知识点&#xff0c;帮助你更好地掌握查找在数据结构和算法中的重要性&#xff0c;进而提升算法解题的能力。接下来让我们开启…

【深度学习】深度学习实验二——前馈神经网络解决上述回归、二分类、多分类、激活函数、优化器、正则化、dropout、早停机制

一、实验内容 实验内容包含要进行什么实验&#xff0c;实验的目的是什么&#xff0c;实验用到的算法及其原理的简单介绍。 1.1 手动实现前馈神经网络解决上述回归、二分类、多分类问题 分析实验结果并绘制训练集和测试集的loss曲线。 原理介绍&#xff1a;回归问题使用的损失函…

在命令行下使用Apache Ant

Apache Ant的帮助文档 离线帮助文档 在<ant的安装目录>/manual下是离线帮助文档 双击index.html可以看到帮助文档的内容&#xff1a; 在线帮助文档 最新发布版本的帮助文档https://ant.apache.org/manual/index.html Apache Ant的命令 ant命令行格式 ant [opt…

Uniapp 入门

创建项目 参考&#xff1a;uni-app创建新页面和页面的配置_uniapp多页面配置-CSDN博客 添加页面 添加路由 显示效果 网址&#xff1a;http://localhost:8080/#/pages/task/taskDetails 参考&#xff1a;uni-app官网 在 HBuilder X 使用命令行引入 uni-ui npm i dcloudio/un…

云开发校园宿舍/企业/部门/物业故障报修小程序源码

微信小程序云开发校园宿舍企业单位部门物业报修小程序源码&#xff0c;这是一款云开发校园宿舍报修助手工具系统微信小程序源码&#xff0c;适用于学校机房、公司设备、物业管理以及其他团队后勤部&#xff0c;系统为简单云开发&#xff0c;不需要服务器域名即可部署&#xff0…

电子电器架构——基于Adaptive AUTOSAR的电子电器架构简析

基于Adaptive AUTOSAR的电子电器架构简析 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…

如何在.NET Core3.1 类库项目中使用System.Windows.Forms

网上说法大多都是直接添加对.Net Framework框架的引用&#xff0c;但是这种方法打包很不友好。于是开始了网络搜索&#xff0c;翻到了微软的文档&#xff0c;才找到直接引用 System.Windows.Froms 程序集的方法。还隐藏的很深&#xff0c;地址&#xff1a;Upgrade a Windows Fo…

【数据结构】:二叉树与堆排序的实现

1.树概念及结构(了解) 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的有一个特殊的结点&#…

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆 前言一. 大小根堆二. 数据流的中位数1.1 初始化1.2 插入操作1.3 完整代码 三. 滑动窗口中位数3.1 在第一题的基础上改造3.2 栈的remove操作 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 大小根堆 先来说下大小根堆是什…

Qt 布局(QLayout 类QStackedWidget 类) 总结

一、QLayout类(基本布局) QLayout类是Qt框架中用于管理和排列QWidget控件的布局类。它提供了一种方便而灵活的方式来自动布局QWidget控件。QLayout类允许您以一种简单的方式指定如何安排控件&#xff0c;并能够自动处理控件的位置和大小&#xff0c;以使其适应更改的父窗口的大…

竞赛选题 深度学习OCR中文识别 - opencv python

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

【LeetCode刷题(数据结构)】:另一颗树的子树

给你两棵二叉树 root 和 subRoot 检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子…

数据库安全-H2 databaseElasticsearchCouchDBInfluxdb漏洞复现

目录 数据库安全-H2 database&Elasticsearch&CouchDB&Influxdb 复现influxdb-未授权访问-jwt 验证H2database-未授权访问-配置不当CouchDB-权限绕过配合 RCE-漏洞CouchDB 垂直权限绕过Couchdb 任意命令执行 RCE ElasticSearch-文件写入&RCE-漏洞Elasticsearch写…