面向对象三大基本特征之三:多态
多态的概述,多态的形式
-
多态的定义:
- 同类型的对象,执行同一个行为,会表现出不同的行为特征
-
多态的常见形式:
父类类型 对象名称 = new 子类构造器; 接口 对象名称 = new 实现类构造器;
-
测试案例:
package com.java.PolymorphicTest; public abstract class Animal { // 为方便测试统一采用public public String name = "父类动物"; public abstract void run(); }
package com.java.PolymorphicTest; public class Dog extends Animal { public String name = "子类狗"; @Override public void run() { System.out.println("小狗跑的很快!"); } }
package com.java.PolymorphicTest; public class Tortoise extends Animal { public String name = "子类乌龟"; @Override public void run() { System.out.println("乌龟跑的很慢!"); } }
package com.java.PolymorphicTest; public class Test { public static void main(String[] args) { Animal d = new Dog(); d.run(); System.out.println(d.name); System.out.println("========================"); Animal t = new Tortoise(); t.run(); System.out.println(d.name); System.out.println("============多态第二个优势,父类作为方法形参,可以将所有子类对象传进来============"); go(d); go(t); } /** * 多态第二个优势 */ public static void go(Animal a) { a.run(); } } /* 小狗跑的很快! 父类动物 ======================== 乌龟跑的很慢! 父类动物 ============多态第二个优势,父类作为方法形参,可以将所有子类对象传进来============ 小狗跑的很快! 乌龟跑的很慢! */
-
多态中成员访问特点:
-
成员方法调用:编译看左边,运行看右边
编译的时候会看向左边父类的成员方法,具体运行时会运行右边子类对象的方法
-
成员变量调用:编译看左边,运行也看左边(多态是侧重行为的多态)
成员变量的调用,不管编译还是运行,都看向父类的一方(如上述测试代码name的值均为父类动物)
-
-
多态的前提:
- 有继承/实现关系
- 有父类引用指向子类对象
- 有方法重写
多态的好处
-
在多态形势下,右边对象可以实现解耦合,便于扩展和维护
组件化的解耦合即组件之间的依赖没有那么强
Animal a = new Dog(); a.run(); //当Dog类换成其他类,即对象发生了变化,后续业务行为随对象而变,后续的代码逻辑无需修改 Animal a = new Cat(); a.run();
-
定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利
参照上方测试案例代码
- 多态下会产生的一个问题:
- 多态下不能使用 子类的独有功能
多态下引用数据类型的类型转换
为了解决多态下不能调用子类独有功能的弊端提出引用数据类型的类型转换
自动类型转换(从子到父):子类对象可以直接赋值给父类类型的变量指向
-
强制类型转换(从父到子):
此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量
作用:可以解决多态下的劣势,可以实现调用子类独有的功能
-
注意:如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
强制类型转换,编译阶段不报错的(注意:有继承或者实现关系编译阶段可以强制转换,但是运行时可能出错)
例如,Dog d = (Dog) a2;编译器认为程序员希望将Animal类的对象a2转换成Dog类型并指向Dog类型的d,因此编译阶段放行,但是在运行时发现,d不是Dog的对象,而是乌龟的对象,指向有问题,张冠李戴,于是报错
-
Java建议强转前使用instanceof关键字判断当前对象的真实类型,然后再强制转换
变量名 instanceof 真实类型 // 判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类型,是则返回true,反之返回false
-
测试代码:
package com.java.PolymorphicDisadvantages; public class Animal { public void run(){ System.out.println("动物可以跑!"); } }
package com.java.PolymorphicDisadvantages; public class Dog extends Animal { @Override public void run() { System.out.println("🐕跑的快"); } /** * 独有功能 */ public void lookDoor() { System.out.println("🐕看门"); } }
package com.java.PolymorphicDisadvantages; public class Tortoise extends Animal { @Override public void run() { System.out.println("🐢跑的很慢!"); } /** * 独有功能 */ public void layEggs() { System.out.println("🐢可以下蛋"); } }
package com.java.PolymorphicDisadvantages; public class Test { public static void main(String[] args) { // 自动类型转换 Animal a = new Dog(); a.run(); // 强制类型转换 Animal a2 = new Tortoise(); Tortoise t = (Tortoise) a2; Dog d = (Dog) a; d.lookDoor(); t.layEggs(); System.out.println(d instanceof Dog); // true System.out.println(t instanceof Tortoise); // true System.out.println(d instanceof Tortoise); // 在编译阶段直接报红,由此可知类型不匹配 } }
多态的综合案例
-
需求:
- 使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备
- 鼠标:被安装时可以完成接入,调用点击功能,拔出功能
- 键盘:被安装时可以完成接入,调用打字功能,拔出功能
-
分析:
- 定义一个USB接口(申明USB设备的规范是:可以接入和拔出)
- 提供两个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能
- 创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行
-
案例代码:
package com.java.Polymorphic_Case; /** * usb接口规范 */ public interface USB { void connect(); void unconnect(); }
package com.java.Polymorphic_Case; /** * 鼠标实现类 */ public class Mouse implements USB { private String goodsName; public Mouse() { } public Mouse(String goodsName) { this.goodsName = goodsName; } public String getGoodsName() { return goodsName; } public void setGoodsName(String name) { this.goodsName = name; } @Override public void connect() { System.out.println(getGoodsName() + "鼠标成功接入"); } @Override public void unconnect() { System.out.println(getGoodsName() + "鼠标成功断开"); } /** * 鼠标独有功能:点击 */ public void onClick() { System.out.println("使用" + getGoodsName() + "鼠标点击了IDEA中的运行按钮"); } }
package com.java.Polymorphic_Case; import java.util.Scanner; /** * 键盘实现类 */ public class KeyBoard implements USB { private String goodsName; public KeyBoard() { } public KeyBoard(String goodsName) { this.goodsName = goodsName; } public String getGoodsName() { return goodsName; } public void setGoodsName(String goodsName) { this.goodsName = goodsName; } @Override public void connect() { System.out.println(getGoodsName() + "键盘成功接入"); } @Override public void unconnect() { System.out.println(getGoodsName() + "键盘成功断开"); } /** * 键盘独有功能:打字 */ public void keyDone() { Scanner scanner = new Scanner(System.in); System.out.println("请敲击键盘"); String str = scanner.next(); System.out.println("使用" + getGoodsName() + "键盘敲击出一串内容:" + str); } }
package com.java.Polymorphic_Case; public class Computer { private String goodsName; public Computer() { } public Computer(String goodsName) { this.goodsName = goodsName; } public String getGoodsName() { return goodsName; } public void setGoodsName(String goodsName) { this.goodsName = goodsName; } /** * 提供安装USB设备的入口 */ public void installUSB(USB usb) { //多态用法 usb.connect(); // 独有功能调用 if (usb instanceof KeyBoard) { KeyBoard k = (KeyBoard) usb; k.keyDone(); } else if (usb instanceof Mouse) { Mouse m = (Mouse) usb; m.onClick(); } usb.unconnect(); } public void start() { System.out.println(getGoodsName() + "电脑开机了"); } }
package com.java.Polymorphic_Case; public class Test { public static void main(String[] args) { Computer c = new Computer(); c.setGoodsName("Thinkpad"); c.start(); USB u = new KeyBoard("HHKB"); c.installUSB(u); USB v = new Mouse("Razer"); c.installUSB(v); } }