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); // trueFish 继承了 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.抽象类和接口在多态中的角色?九.实战演练灵活的支付系统背景你正在开发一个电商系统的支付模块。系统需要支持多种支付方式支付宝支付、微信支付、银行卡支付。未来还可能增加新的支付方式。每种支付方式的处理流程略有不同但都包含两个基本动作扣款和退款。需求定义一个支付接口Payment包含两个方法boolean pay(double amount)支付指定金额返回是否成功。void refund(double amount)退款指定金额直接输出退款信息即可。实现三种具体的支付类分别命名为AliPay、WeChatPay、CardPay都实现Payment接口。每个类的构造方法可以接收必要的身份标识。pay方法中输出“通过 [支付方式] 支付了 xx 元”并返回true。refund方法中输出“通过 [支付方式] 退款了 xx 元”。多态场景创建一个PaymentProcessor类其中包含一个void processPayment(Payment payment, double amount)方法接收Payment接口引用完成支付操作调用pay。额外提供一个void cancelOrder(Payment payment, double amount)方法接收Payment引用完成退款操作调用refund。特殊需求对于CardPay类额外增加一个方法void deductPoints(int points)表示使用银行卡积分抵扣。在processPayment方法中如果传入的是CardPay类型除了支付外自动调用deductPoints(100)模拟本次支付赠送积分或使用积分。要求使用instanceof检查并向下转型。测试场景编写主类PaymentTest创建支付宝、微信、银行卡三种支付对象。使用PaymentProcessor分别处理它们的支付多态方式调用。单独测试退款方法多态方式。特意传入银行卡支付对象观察积分抵扣逻辑是否正确执行。扩展要求可选新增一种支付方式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(aliceexample.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); } }十.小结这个实战题目涵盖了多态的核心应用场景接口统一、动态绑定、类型判断与扩展性。动手敲一遍代码你会更清楚地理解“父类引用指向子类对象”到底带来了怎样的灵活性。现在就去运行它然后试着增加一种新的支付方式吧——你会看到多态让代码几乎不需要改动就能自然扩展。