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

Java多态从入门到通关:考点精讲+面试考点+项目实战

目录

一.引言

(一).什么是多态?

(二).多态在面向对象三大特征中的重要性

(三).为什么多态重要?解决了什么问题?

二.多态的两种类型

(一).编译时多态

(二).运行时多态

(三).二者区别对比

三.运行时多态的实现条件

四.核心机制讲解

(一).动态绑定

(二).JVM在运行时决定调用哪个方法

(三).instanceof关键字的使用

(四).向上转型vs向下转型,ClassCastException风险

五.接口与抽象类中的实现

(一).通过接口实现多态

(二).抽象类VS接口在多态场景下的选择

(三).代码对比

六.多态的优缺点

七.常见误区与陷阱

八.总结

九.面试常见考点

十.实战演练


一.引言

(一).什么是多态?

简单来说,同一个方法,不同的对象调用所产生的结果也不一样,就叫多态。

我们来看这样一段代码:

class Animal{ String name; int age; public Animal(String name,int age){ this.name = name; this.age = age; } public void move(){ System.out.println(this.name + "正在跑"); } } //Fish类继承Animal类,并且重写move()方法 class Fish extends Animal{ public Fish(String name,int age){ super(name,age); } public void move(){ System.out.println(this.name + "正在游泳"); } } //Dog类也继承Animal类,并且重写move()方法 class Dog extends Animal { public Dog(String name,int age){ super(name,age); } public void move(){ System.out.println(this.name + "正在跑步"); } } public class Test{ public static void main(String[] args){ Animal fish = new Fish("彩鳞",1);//这里发生了向上转型 Animal dog = new Dog("小白",4); fish.move(); dog.move(); } }

运行结果如下:

同样调用move()方法,但是对象不同,一个是fish,一个是dog,结果也不同,这个就 叫多态

我们注意到,上面的代码发生了这几件事情:

1.Dog类和Fish类都继承了Animal类

2.Dog类和Fish类重写了Animal类的mova()方法
3.发生了向上转型。(下面会讲解,这里先知道有这个就可以)

这就是触发多态的条件:

1.有继承关系

2.子类重写父类的方法

3.发生了向上绑定

(二).多态在面向对象三大特征中的重要性

面向对象三大特征的关系为:

封装(基础) → 继承(前提) → 多态(核心体现),多态建立在封装和继承之上,是最能 体现面向对象的设计价值的特征。

如果没有多态,我们就只能这样设计:

class Animal{ String name; int age; public Animal(String name,int age){ this.name = name; this.age = age; } } //Fish类继承Animal类 class Fish extends Animal{ public Fish(String name,int age){ super(name,age); } } //Dog类也继承Animal类 class Dog extends Animal { public Dog(String name,int age){ super(name,age); } } public class Test{ public static void main(String[] args){ Fish fish = new Fish("彩鳞",1);/ Dog dog = new Dog("小白",4); if (animal instanceof Fish) { System.out.println(animal.name + "正在游泳"); } else if (animal instanceof Dog) { System.out.println(animal.name + "正在跑步"); } } }

每次新增一个类,都要判断新的对象隶属于哪个类,大大降低了代码的灵活度。

总结来说,封装实现了对内部实现的隐藏保护数据安全。继承实现了代码的复 用,建立类之间的层级关系,多态让程序灵活应对变化,对外保持统一接口。

(三).为什么多态重要?解决了什么问题?

多态的核心价值:

1.降低耦合,对外统一接口。

无论什么动物,只需要animal.move();

2.对扩展开放,对修改关闭。

比如代码新增一个bird类,我们只需要在bird类中也重写move方法,完全不用 改动其他的move方法

3.是所有设计模式的基础

几乎所有设计模式的核心都依赖多态来实现灵活替换。

补充对比:多态的不足之处:

1.多态的代码效率比较低

2.成员变量和构造方法不能使用多态

二.多态的两种类型

(一).编译时多态

编译时多态的代表是方法的重载。编译器在编译是就能确定调用哪个方法。

重载是在一个类中,方法名相同,参数不同(参数个数不同或参数的顺序不同或参数的类型不同),通过传入不同的参数来调用对应方法。

这里声明一下,学术界对于重载算不算多态存在争议。因为多态是不同对象调用同一个方法所产生的不同的行为。而重载是同一个对象的不同方法,所以我们认为重载是“最弱 的”多态

我们可以理解为,重载是同一个接口(方法名),不同的表现形式。

(二).运行时多态

重写是运行时多态的经典代表。JVM 在运行阶段才能确定调用哪个方法。

Animal fish = new Fish("彩鳞", 1); Animal dog = new Dog("小白", 4); fish.move(); // 运行时才知道:fish 实际是 Fish,调用 Fish 的 move() dog.move(); // 运行时才知道:dog 实际是 Dog, 调用 Dog 的 move()

编译器看到的是Animal类型,它不知道具体是哪个子类,只有程序真正跑起来之后,JVM 才查看对象的真实类型,再决定调用谁的方法。

(三).二者区别对比

编译时多态和运行时多态对比
编译时多态运行时多态
实现方式方法重载方法重写

决定时机

编译阶段运行阶段
判断依据根据参数列表对象的实际类型
是否需要继承不需要需要
灵活性
典型场景同一个类中的多种操作向上转型

三.核心机制讲解

(一).动态绑定

1.动态绑定的定义:

在继承的大条件下,当运行代码的时候,调用了父类和子类中重写的那个方法,结果实际调用了子类的方法,我们把这个情况叫做叫做动态绑定。

2.动态绑定的触发条件:

(1).父类的引用 指向了 子类的对象

(2).父类和子类存在重写的方法

3.动态绑定注意事项:

如果父类的构造方法中,调用了父类和子类发生重写的方法,那么会发生动态绑定

(二).instanceof关键字的使用

我们可以通过 inStanceOf 关键字来判断对象是否属于类,是则返回true,否则返回flase;

//定义Animal类,设置name和age属性,给构造方法和move()方法 class Animal { String name; int age; public Animal(String name, int age) { this.name = name; this.age = age; } public void move() { System.out.println(this.name + " 正在移动"); } } //Fish类继承Animal类,在构造方法中调用父类的构造方法,重写move()方法,给呼吸的方法 class Fish extends Animal { public Fish(String name, int age) { super(name, age); } @Override public void move() { System.out.println(this.name + " 正在游泳"); } public void breatheInWater() { System.out.println(this.name + " 在水中呼吸"); } } //Dog类继承Animal类,在构造方法中调用父类的构造方法,重写move()方法,给叫的方法 class Dog extends Animal { public Dog(String name, int age) { super(name, age); } @Override public void move() { System.out.println(this.name + " 正在跑步"); } public void bark() { System.out.println(this.name + " 汪汪叫"); } } //定义Fish和Dog类的对象,判断对象是否属于某个类 public class Test { public static void main(String[] args) { Animal fish = new Fish("彩鳞", 1); Animal dog = new Dog("小白", 4); // 判断对象是否属于某个类 System.out.println("=== 基本判断 ==="); System.out.println(fish instanceof Fish); // true System.out.println(fish instanceof Animal); // true,Fish 继承了 Animal System.out.println(fish instanceof Dog); // false // 实际用途:向下转型前的安全检查 // 父类引用无法直接调用子类独有的方法 // fish.breatheInWater(); 编译报错,Animal 没有这个方法 System.out.println("\n=== 安全向下转型 ==="); if (fish instanceof Fish) { Fish f = (Fish) fish; // 确认是 Fish 之后再强转,安全 f.breatheInWater(); // 现在可以调用 Fish 独有的方法 } if (dog instanceof Dog) { Dog d = (Dog) dog; d.bark(); // 调用 Dog 独有的方法 } // 不用 instanceof 直接强转会怎样? System.out.println("\n=== 不检查直接强转的后果 ==="); try { Dog d = (Dog) fish; // fish 实际是 Fish,强转成 Dog d.bark(); } catch (ClassCastException e) { System.out.println("报错了:" + e.getMessage()); // 运行时抛出异常 } } }

运行结果如下:

(三).向上转型vs向下转型,ClassCastException风险

向上转型:

1.定义:

向上引用指得是子类的对象 赋值给 父类的引用,例如:

Animal animal = new Dog();

2.特点:

(1).安全,无风险

(2).引用是父类,对象是子类

(3).可以使用父类的属性和方法(如果子类重写了,优先执行子类的重写方法 ),但是不能使用子类独有的属性。

3.三种实现方法:

(1).直接赋值:

Animal animal = new Dog();

(2).方法传参:

public void test(Animal animal){ }

(3).返回值:

public Animal test(){ Dog dog = new Dog(); return dog; } publlic static void main(){ Animal animal = test(); }

向下转型:

1.定义:

向下转型是重新定义一个引用,创建一个新的子类与父类引用指向同一个对象。通过强制类型转换把父类的引用指向强制类型转换后的引用,代码如下:

Animal animal = new Dog();//向上引用 Dog dog = (Dog)animal;//向下引用 dog.worf;//强转后的引用可以调用子类本身的方法,也可以调用父类继承下来的方法

2.注意事项:

(1).不是所有的向下转换都能成功(强制类型转换存在风险)

(2).向下转换可以让引用使用子类对象的成员方法

四.接口与抽象类中的实现

(一).通过接口实现多态

// 定义接口 interface Animal { void move(); // 接口中的方法默认是 public abstract void eat(); } // Fish 实现 Animal 接口 class Fish implements Animal { String name; public Fish(String name) { this.name = name; } @Override public void move() { System.out.println(this.name + " 正在游泳"); } @Override public void eat() { System.out.println(this.name + " 在吃水草"); } } // Dog 实现 Animal 接口 class Dog implements Animal { String name; public Dog(String name) { this.name = name; } @Override public void move() { System.out.println(this.name + " 正在跑步"); } @Override public void eat() { System.out.println(this.name + " 在啃骨头"); } } // Bird 实现 Animal 接口 class Bird implements Animal { String name; public Bird(String name) { this.name = name; } @Override public void move() { System.out.println(this.name + " 正在飞翔"); } @Override public void eat() { System.out.println(this.name + " 在吃虫子"); } } public class Test { // 参数类型是接口,任何实现了 Animal 接口的对象都可以传进来 public static void doAction(Animal animal) { animal.move(); animal.eat(); } public static void main(String[] args) { // 接口引用指向实现类对象,和父类引用指向子类对象是同样的道理 Animal fish = new Fish("彩鳞"); Animal dog = new Dog("小白"); Animal bird = new Bird("小黄"); System.out.println("=== 直接调用 ==="); fish.move(); dog.move(); bird.move(); System.out.println("\n=== 通过方法统一调用 ==="); doAction(fish); System.out.println("---"); doAction(dog); System.out.println("---"); doAction(bird); } }

(二).抽象类VS接口

1.抽象类的概念和实现:

抽象类的概念:抽象类是被abstract修饰的类,抽象类不能被实例化,只能作为其他类的父类而使用。

2.抽象方法:抽象方法是被abstract修饰的方法,抽象方法可以没有具体的实现

(1).抽象类和抽象方法的实现:

public abstract class Animal{ public String name; public int age; public abstract eat(){ System.out.println(this.name + "正在吃……"); } }

(2).抽象方法和抽象类的关系:

一个抽象类中可以没有抽象方法,但是抽象方法必须在抽象类中。抽象类中可以包含其他普通的成员方法和成员变量

3.抽象类和抽象方法的注意事项:

(1).我们不能实例化抽象类,因为抽象类是不完整的

(2).抽象类的抽象方法在继承后必须重写,否则违反了非抽象类中不能有抽象方法的准则

(3).抽象类就是为了被继承而生

(4).抽象方法必须满足重写所需要的五个条件:不能被final,static,private修饰,可以构成赋值关系,权限限定符

// 定义抽象类 abstract class Shape { String color; public Shape(String color) { this.color = color; } // 抽象方法:没有方法体,子类必须重写 public abstract double getArea(); public abstract double getPerimeter(); // 普通方法:子类直接继承,不需要重写 public void printInfo() { System.out.println("图形:" + this.getClass().getSimpleName() + ",颜色:" + this.color + ",面积:" + this.getArea() + ",周长:" + this.getPerimeter()); } } // 圆形 class Circle extends Shape { double radius; public Circle(String color, double radius) { super(color); this.radius = radius; } @Override public double getArea() { return Math.PI * radius * radius; } @Override public double getPerimeter() { return 2 * Math.PI * radius; } } // 矩形 class Rectangle extends Shape { double width; double height; public Rectangle(String color, double width, double height) { super(color); this.width = width; this.height = height; } @Override public double getArea() { return width * height; } @Override public double getPerimeter() { return 2 * (width + height); } } // 三角形 class Triangle extends Shape { double a, b, c; // 三条边 public Triangle(String color, double a, double b, double c) { super(color); this.a = a; this.b = b; this.c = c; } @Override public double getArea() { // 海伦公式 double s = (a + b + c) / 2; return Math.sqrt(s * (s - a) * (s - b) * (s - c)); } @Override public double getPerimeter() { return a + b + c; } } public class Test { public static void main(String[] args) { // 抽象类不能实例化 // Shape shape = new Shape("红色"); // 编译直接报错 // 抽象类引用指向子类对象 Shape circle = new Circle("红色", 5); Shape rectangle = new Rectangle("蓝色", 4, 6); Shape triangle = new Triangle("绿色", 3, 4, 5); System.out.println("=== 各自调用抽象方法 ==="); // 同一个方法名,不同子类,结果不同 —— 多态 System.out.printf("圆形面积:%.2f%n", circle.getArea()); System.out.printf("矩形面积:%.2f%n", rectangle.getArea()); System.out.printf("三角形面积:%.2f%n", triangle.getArea()); System.out.println("\n=== 调用继承来的普通方法 ==="); // printInfo() 是抽象类里的普通方法,三个子类都没有写这个方法 // 但是都能用,因为继承了抽象类 circle.printInfo(); rectangle.printInfo(); triangle.printInfo(); } }

八.面试常见考点

1. 什么是多态?多态有哪几种类型?

2. 多态的实现条件是什么?

3. 重载和重写(的区别?

4. 什么是动态绑定?与静态绑定有什么区别?

5.instanceof关键字的作用与多态中的使用场景

6.static修饰的方法能重写嘛?为什么?

7.. 多态的好处与弊端?

8.抽象类和接口在多态中的角色?

九.实战演练

灵活的支付系统

背景

你正在开发一个电商系统的支付模块。系统需要支持多种支付方式:支付宝支付微信支付银行卡支付。未来还可能增加新的支付方式。每种支付方式的处理流程略有不同,但都包含两个基本动作:扣款退款

需求

  1. 定义一个支付接口Payment,包含两个方法:

    • boolean pay(double amount):支付指定金额,返回是否成功。

    • void refund(double amount):退款指定金额(直接输出退款信息即可)。

  2. 实现三种具体的支付类,分别命名为AliPayWeChatPayCardPay,都实现Payment接口。

    • 每个类的构造方法可以接收必要的身份标识。

    • pay方法中:输出“通过 [支付方式] 支付了 xx 元”,并返回true(。

    • refund方法中:输出“通过 [支付方式] 退款了 xx 元”。

  3. 多态场景

    • 创建一个PaymentProcessor类,其中包含一个void processPayment(Payment payment, double amount)方法,接收Payment接口引用,完成支付操作(调用pay)。

    • 额外提供一个void cancelOrder(Payment payment, double amount)方法,接收Payment引用,完成退款操作(调用refund)。

  4. 特殊需求

    • 对于CardPay类,额外增加一个方法void deductPoints(int points),表示使用银行卡积分抵扣。

    • processPayment方法中,如果传入的是CardPay类型,除了支付外,自动调用deductPoints(100)(模拟本次支付赠送积分,或使用积分)。

    • 要求使用instanceof检查并向下转型。

  5. 测试场景:编写主类PaymentTest

    • 创建支付宝、微信、银行卡三种支付对象。

    • 使用PaymentProcessor分别处理它们的支付(多态方式调用)。

    • 单独测试退款方法(多态方式)。

    • 特意传入银行卡支付对象,观察积分抵扣逻辑是否正确执行。

  6. 扩展要求(可选):

    • 新增一种支付方式CryptoPayment(加密货币支付)。pay方法中输出“通过加密货币支付了 xx 元”,refund输出对应退款。

    • 在不修改PaymentProcessor和主类核心逻辑的前提下,验证新的支付方式能否被无缝集成。

框架如下:

// Payment.java public interface Payment { boolean pay(double amount); void refund(double amount); } // AliPay.java public class AliPay implements Payment { private String account; public AliPay(String account) { this.account = account; } // TODO 实现pay和refund方法 } // WeChatPay.java public class WeChatPay implements Payment { private String openId; public WeChatPay(String openId) { this.openId = openId; } // TODO 实现pay和refund方法 } // CardPay.java public class CardPay implements Payment { private String cardNumber; public CardPay(String cardNumber) { this.cardNumber = cardNumber; } // TODO 实现pay和refund方法 // 额外方法 public void deductPoints(int points) { System.out.println("银行卡积分抵扣 " + points + " 积分"); } } // PaymentProcessor.java public class PaymentProcessor { public void processPayment(Payment payment, double amount) { // TODO: 调用pay方法,并利用instanceof处理CardPay特有的积分抵扣 } public void cancelOrder(Payment payment, double amount) { // TODO: 调用refund方法 } } // PaymentTest.java public class PaymentTest { public static void main(String[] args) { PaymentProcessor processor = new PaymentProcessor(); Payment alipay = new AliPay("alice@example.com"); Payment wechat = new WeChatPay("wx_123456"); Payment card = new CardPay("6222****1234"); System.out.println("=== 支付场景 ==="); processor.processPayment(alipay, 100.0); processor.processPayment(wechat, 50.0); processor.processPayment(card, 200.0); System.out.println("=== 退款场景 ==="); processor.cancelOrder(alipay, 20.0); processor.cancelOrder(card, 50.0); Payment crypto = new CryptoPayment("0x..."); processor.processPayment(crypto, 300.0); } }

十.小结

这个实战题目涵盖了多态的核心应用场景:接口统一、动态绑定、类型判断与扩展性。动手敲一遍代码,你会更清楚地理解“父类引用指向子类对象”到底带来了怎样的灵活性。现在就去运行它,然后试着增加一种新的支付方式吧——你会看到,多态让代码几乎不需要改动就能自然扩展。

http://www.jsqmd.com/news/827044/

相关文章:

  • 基于AI-Dial-Core构建企业级对话AI核心:从工具抽象到安全实践
  • 现代SaaS应用全栈开发:从Next.js、Prisma到Stripe的实战样板间解析
  • 定制你的专属探针:PEG-锰基纳米材料,为精准科研而生
  • AI智能体技能库开发实战:从工具调用到系统集成
  • 基于Red Hat UBI构建企业级容器运维镜像:OpenClaw-UBI深度解析与实践
  • 终极游戏键位重映射工具指南:如何用Hitboxer解决键盘输入冲突问题
  • 一个‘浮地’电路,解决你的高共模电压测量难题(附TL431负压生成电路)
  • 【数字孪生实战案例】三维场景中怎样点击飞线,唤起弹窗并加载匹配的关联数据?~山海鲸可视化
  • 紧急通知:地质项目交付周期压缩迫在眉睫——用NotebookLM替代传统笔记整理,单项目节省22.6工时(附审计级日志)
  • iOS 17-26越狱完整指南:安全解锁iPhone隐藏功能的终极教程
  • 2026年游乐设备采购TOP榜单:最新行业趋势解析
  • 第5章 集群初始化
  • 基于MCP与ADB实现AI智能体远程控制安卓手机的实践指南
  • ncmdump实用指南:3步高效解锁网易云音乐NCM格式的完整解决方案
  • 2026年5月14隔夜暗盘挂单排行榜
  • WinGet安装工具:PowerShell自动化部署的架构解析与实践指南
  • 开源商业技能库OpenClaw:构建结构化知识体系与高效学习路径
  • Llama 2 WebUI部署指南:从零搭建图形化大模型交互界面
  • LLM智能体开发指南:从Awesome List到项目实战
  • 告别手动抢红包!用Kotlin写一个Android微信红包监听助手(附完整代码)
  • HyperBus接口技术解析与高性能NOR闪存应用
  • 开源项目脚手架:用oss-forge一键生成现代化项目基础设施
  • 解密Java静态调用图:架构师的高效分析实战
  • 终极游戏增强方案:3步解决经典魔兽争霸3兼容性问题
  • JSON格式强制输出失败,深度解析DeepSeek-R1/V3模型token级响应机制与schema约束绕过方案
  • 仅1月Accepted!恭喜北大学者独作发表Nature子刊(IF 10.1)!
  • 2026年升级:精油OEM加工厂家 - 品牌推广大师
  • NotebookLM心理学研究辅助:为什么92%的心理学博士生漏用了“语义锚定”功能?
  • 基于RAG的Obsidian智能知识库:本地部署与优化实战
  • Cura 3D打印切片软件终极指南:从零开始掌握专业级切片技术