当前位置: 首页 > news >正文

Java中的多态有什么用?

Java中的多态有什么用?

举一个多态的例子:
Deque<ListNode> stack = new LinkedList<ListNode>();
初看可能觉得“直接用 LinkedList 声明变量不更简单吗?”,但这行代码背后其实蕴含了 Java 多态的核心思想和“面向接口编程”的设计原则。本文将从实际场景出发,拆解这种写法的优势,以及为什么不推荐其他替代方案。

一、先搞懂:这行代码里的多态是什么?

要理解这种写法,首先需要明确 多态在 Java 集合中的体现

Deque<ListNode> stack = new LinkedList<ListNode>(); 中:

  • 左侧 Deque<ListNode>:是 接口类型(Deque 是双端队列接口,定义了 push()pop()add() 等标准操作);
  • 右侧 new LinkedList<ListNode>():是 接口的实现类对象(LinkedList 是 Deque 接口的具体实现,用链表结构实现了双端队列的所有功能)。

这完全符合 Java 多态的核心逻辑:父类型(接口)引用指向子类型(实现类)对象。就像“用‘交通工具’的引用指向‘汽车’对象”,我们只关心“交通工具能移动”的核心能力,不关心“汽车是用轮子还是翅膀”的具体实现。

二、为什么推荐用“接口声明 + 实现类实例化”?

选择 Deque<ListNode> stack = new LinkedList<>(),而不是直接用 LinkedList 声明变量,核心优势在于 解耦与灵活性。我们通过一个实际场景对比,就能直观感受到这种设计的价值。

场景背景

假设我们需要实现一个“用栈存储链表节点,后续弹出节点处理”的功能(比如链表中删除倒数第 N 个节点的场景)。

反例:用具体类 LinkedList 声明变量(耦合度高)

如果一开始直接用 LinkedList 声明变量,代码会写成这样:

// 用具体类 LinkedList 声明变量
LinkedList<ListNode> stack = new LinkedList<>();// 业务逻辑:往栈中存链表节点
stack.push(head);       // 存头节点
stack.push(head.next);  // 存下一个节点// 定义方法:接收 LinkedList 类型的参数
public void processStack(LinkedList<ListNode> stack) {while (!stack.isEmpty()) {ListNode node = stack.pop();  // 弹出节点并处理System.out.println("处理节点值:" + node.val);}
}// 调用方法
processStack(stack);
问题:想换更高效的 ArrayDeque 时,要“牵一发而动全身”

后来我们发现,ArrayDeque(同样实现了 Deque 接口)的效率比 LinkedList 更高(ArrayDeque 基于数组实现,随机访问更快),想把 LinkedList 换成 ArrayDeque

但因为最初用了 具体类声明变量,我们必须修改 所有和 stack 相关的地方

  1. 变量声明:ArrayDeque<ListNode> stack = new ArrayDeque<>();
  2. 方法参数:public void processStack(ArrayDeque<ListNode> stack) { ... }
  3. 方法调用:如果其他地方调用 processStack,传入的参数类型也必须从 LinkedList 改成 ArrayDeque
  4. 返回值:如果有方法返回 LinkedList<ListNode>,也要改成 ArrayDeque<ListNode>

这种修改方式不仅繁琐,还容易遗漏代码导致 Bug——这就是“代码耦合度高”的代价:代码和具体实现类绑定死了,失去了灵活性。

正例:用接口 Deque 声明变量(灵活解耦)

如果一开始就用 Deque 接口声明变量,代码会是这样:

// 用接口 Deque 声明变量,实例化用 LinkedList
Deque<ListNode> stack = new LinkedList<>();// 业务逻辑不变(push、pop 是 Deque 定义的方法)
stack.push(head);
stack.push(head.next);// 方法参数用 Deque 接口(不依赖具体实现)
public void processStack(Deque<ListNode> stack) {  // 这里不用改!while (!stack.isEmpty()) {ListNode node = stack.pop();              // 这里也不用改!System.out.println("处理节点值:" + node.val);}
}// 调用方法不变
processStack(stack);
优势:换 ArrayDeque 只需改 1 行代码

当我们想换成 ArrayDeque 时,只需要修改实例化的部分,其他所有代码完全不变:

// 只改这一行:把 new LinkedList 换成 new ArrayDeque
Deque<ListNode> stack = new ArrayDeque<>();// 下面的业务逻辑、方法参数、调用方式全不变!
stack.push(head);
processStack(stack);  // 正常运行,因为 processStack 接收的是 Deque 类型

这种写法的核心价值在于:代码只依赖“功能规范”(Deque 定义的方法),不依赖“具体实现”(LinkedList 或 ArrayDeque)。无论底层用哪种实现类,只要符合 Deque 接口的规范,上层代码就无需调整。

三、其他常见写法的问题:为什么不推荐?

除了“接口声明 + 实现类实例化”,我们还可能想到其他写法,但这些写法都存在明显缺陷。

1. 为什么不直接用 Deque<ListNode> stack = new Deque<>();

错误原因:Deque 是接口,不能直接实例化(new)。

Java 中,接口的作用是“定义规范”,它只包含方法的声明(没有方法体),本身没有具体实现逻辑。就像“不能直接造一个‘交通工具’,只能造‘汽车’或‘自行车’(交通工具的实现类)”,接口必须通过它的实现类(如 LinkedList、ArrayDeque)才能创建对象。

2. 为什么不推荐 LinkedList<ListNode> stack = new LinkedList<>();

问题核心:耦合度高,限制未来扩展。

如前文反例所示,这种写法会让变量 stackLinkedList 这个具体实现类强绑定。如果后续想更换实现类(比如换成 ArrayDeque、ConcurrentLinkedDeque 等),就必须修改所有声明、参数、返回值,维护成本极高。

简单说:用具体类声明变量,相当于“把代码焊死在某个实现上”,失去了多态带来的灵活性。

3. 为什么 Deque<ListNode> stack = new ArrayDeque<>(); 是可行的?

这是完全可行的!因为 ArrayDeque 也是 Deque 接口的实现类,符合“接口声明 + 实现类实例化”的原则。

选择 LinkedList 还是 ArrayDeque,只取决于具体业务需求:

  • LinkedList:适合频繁在两端插入/删除数据的场景(链表结构的优势);
  • ArrayDeque:适合频繁随机访问、对效率要求高的场景(数组结构的优势)。

但无论选哪种实现类,用 Deque 接口声明变量的写法始终是最优的——它保证了代码的灵活性和可维护性。

四、总结:面向接口编程的核心思想

通过 Deque<ListNode> stack = new LinkedList<>(); 这个例子,我们能提炼出 Java 开发中“面向接口编程”的核心原则:

  1. 依赖抽象,不依赖具体:上层代码只关心“需要什么功能”(接口定义的方法),不关心“功能如何实现”(实现类的细节);
  2. 解耦与灵活:更换实现类时,只需修改实例化部分,上层代码无需调整;
  3. 提高可读性:通过接口类型(如 Deque),其他开发者能快速理解变量的核心功能(双端队列),而不用关心底层是链表还是数组实现。

这种思想不仅适用于集合框架,在整个 Java 开发中都至关重要(比如 Spring 中的依赖注入、接口回调等)。掌握它,能让你的代码更健壮、更易维护,也更符合工业级开发的规范。


文章转载自:

http://MJpVspCF.xcfmh.cn
http://o4uiIDDB.xcfmh.cn
http://VwxKkD96.xcfmh.cn
http://NQHkvZnI.xcfmh.cn
http://3kuOTjEk.xcfmh.cn
http://iOMk2q96.xcfmh.cn
http://y2gGCODn.xcfmh.cn
http://ULH0ZpoM.xcfmh.cn
http://5jPuINnU.xcfmh.cn
http://Jnl9LCQJ.xcfmh.cn
http://IFCkTvTG.xcfmh.cn
http://Cgf2gZZ2.xcfmh.cn
http://aWM39LPY.xcfmh.cn
http://F7Ll2Qft.xcfmh.cn
http://l2Z4HTi0.xcfmh.cn
http://MltvNk3K.xcfmh.cn
http://HSweA8ww.xcfmh.cn
http://oZBnun5B.xcfmh.cn
http://7r4oPA1O.xcfmh.cn
http://rjMeAgFd.xcfmh.cn
http://ZFy9jGYn.xcfmh.cn
http://wq7ggcOb.xcfmh.cn
http://QrcJUdwk.xcfmh.cn
http://9q9PKKJ6.xcfmh.cn
http://eFgjkmgT.xcfmh.cn
http://N8fxHDhG.xcfmh.cn
http://ZfsmwnsX.xcfmh.cn
http://nD60ZVTa.xcfmh.cn
http://mFiASGhj.xcfmh.cn
http://FcTBq40D.xcfmh.cn
http://www.dtcms.com/a/368800.html

相关文章:

  • 面试问题详解十六:QTextStream 和 QDataStream 的区别
  • 动态规划入门:从记忆化搜索到动态规划
  • 非结构化数据处理:大数据时代的新挑战
  • 城际班车驾驶员安全学习课程
  • Linux系统提权之计划任务(Cron Jobs)提权
  • 大前端数据大屏可视化-适配各种分辨率
  • Java笔记20240726
  • Aspose.Words for .NET 25.7:支持自建大语言模型(LLM),实现更安全灵活的AI文档处理功能
  • 怎样利用AE统计数据优化安防芯片ISP的图像质量?
  • 基于Python读取多个excel竖向拼接为一个excel
  • 深入解析汇编语言的奥秘
  • C++语言程序设计——06 字符串
  • 十二、软件系统分析与设计
  • flink 伪代码
  • AGENTS.md: AI编码代理的开放标准
  • 代码可读性的详细入门
  • 单元测试:Jest 与 Electron 的结合
  • 02-Media-5-mp4demuxer.py 从MP4文件中提取视频和音频流的示例
  • K8s访问控制(一)
  • 动物专家?单词测试!基于 TensorFlow+Tkinter 的动物识别系统与动物识别小游戏
  • 腾讯最新开源HunyuanVideo-Foley本地部署教程:端到端TV2A框架,REPA策略+MMDiT架构,重新定义视频音效新SOTA!
  • GD32入门到实战33--用单片机内部FLASH保护产品参数
  • Python的RSS/Atom源解析库feedparser
  • 抓虫:loongarch64架构selinux强防开启程序执行报错execmod
  • 酷柚易汛ERP 2025-09-05系统升级日志
  • STM32——WDG看门狗
  • Redis 发布订阅:社区的 “通知栏与分类订阅” 系统
  • WordPress性能优化全攻略:从插件实战到系统级优化
  • [新启航]激光频率梳 3D 轮廓测量 - 蓝光机械 3D 扫描的工作原理及优缺点
  • 3DEXPERIENCE平台五大实用技巧指南