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

多态:面向对象编程的重要特性

摘要:本文全面阐述了面向对象编程中多态这一关键特性,从多态的形式、使用场景、定义及前提条件出发,深入分析其运行特点、弊端,详细介绍引用类型转换相关知识,并通过综合练习强化对多态的理解与应用,为深入掌握面向对象编程提供了有力支撑。

关键词:多态;继承;方法重写;引用类型转换
参考资料::黑马程序员

一、引言

多态作为面向对象的第三大特性,在继承或实现关系中发挥重要作用,极大地增强了程序的灵活性与扩展性。本文将系统地探讨多态的各个方面。

二、多态的形式

多态在继承或实现关系中得以体现,其常见格式为:

父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();

多态存在的前提是具有继承关系,子类对象能够赋值给父类类型变量。例如,Animal为动物类型,Cat为猫类型,Cat继承自Animal,那么Cat对象可赋值给Animal类型变量。

三、多态的使用场景

在未使用多态时,如register方法若仅能接收学生对象,对于TeacherAdministrator对象则需定义不同的register方法。

public void register(Student s){}

而借助多态,register方法的形参可定义为共同的父类Person
当方法形参为类时,可传递该类的所有子类对象;当形参为接口时,可传递该接口的所有实现类对象(后续学习),并且多态能够依据传递的不同对象调用不同类中的方法。

以代码示例说明:

// 父类
public class Person {private String name;private int age;// 空参构造public Person() {}// 带全部参数的构造public Person(String name, int age) {this.name = name;this.age = age;}// get和set方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void show() {System.out.println(name + ", " + age);}
}// 子类1
public class Administrator extends Person {@Overridepublic void show() {System.out.println("管理员的信息为:" + getName() + ", " + getAge());}
}// 子类2
public class Student extends Person {@Overridepublic void show() {System.out.println("学生的信息为:" + getName() + ", " + getAge());}
}// 子类3
public class Teacher extends Person {@Overridepublic void show() {System.out.println("老师的信息为:" + getName() + ", " + getAge());}
}// 测试类
public class Test {public static void main(String[] args) {// 创建三个对象,并调用register方法Student s = new Student();s.setName("张三");s.setAge(18);Teacher t = new Teacher();t.setName("王建国");t.setAge(30);Administrator admin = new Administrator();admin.setName("管理员");admin.setAge(35);register(s);register(t);register(admin);}// 这个方法既能接收老师,又能接收学生,还能接收管理员// 只能把参数写成这三个类型的父类public static void register(Person p) {p.show();}
}

PS:@Override 注解最主要的作用是帮助编译器进行检查,确保被注解的方法确实重写了父类中的方法。如果一个方法使用了 @Override 注解,但父类中不存在对应的方法,编译器会报错。这样可以避免因方法签名错误而导致的意外行为

四、多态的定义和前提

多态指同一行为具有多个不同表现形式。例如,CatDog同为动物,都有“吃”这一行为,但具体表现不同。
其前提条件如下:

  1. 继承或实现关系:类之间需存在继承或实现关系。
  2. 方法的重写:子类对父类方法进行重写,若无重写,多态意义不大。
  3. 父类引用指向子类对象:即采用父类类型 变量名 = new 子类类型();的格式,其中父类类型指子类对象继承的父类类型或实现的父接口类型。

五、多态的运行特点

  1. 调用成员变量时:编译阶段依据左边父类类型判断是否存在该成员变量,运行阶段也使用左边父类类型的成员变量值。
  2. 调用成员方法时:编译阶段检查左边父类是否存在该方法,运行阶段则执行右边子类重写后的方法。

以代码示例说明:

Fu f = new Zi();
// 编译看左边的父类中有没有name这个属性,没有就报错
// 在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);// 编译看左边的父类中有没有show这个方法,没有就报错
// 在实际运行的时候,运行的是子类中的show方法
f.show();

六、多态的弊端

多态在编译阶段依赖左边父类类型,这导致若子类具有独有的功能,多态写法无法直接访问。例如:

class Animal {public void eat() {System.out.println("动物吃东西!");}
}class Cat extends Animal {  public void eat() {  System.out.println("吃鱼");  }  public void catchMouse() {  System.out.println("抓老鼠");  }  
}  class Dog extends Animal {  public void eat() {  System.out.println("吃骨头");  }  
}class Test {public static void main(String[] args) {Animal a = new Cat();a.eat();a.catchMouse();// 编译报错,编译看左边,Animal没有这个方法}
}

七、引用类型转换

7.1 为什么要转型

多态无法直接调用子类独有的方法,为实现对这些方法的调用,需要进行转型。回顾基本数据类型转换,存在自动转换(范围小的赋值给范围大的,如double d = 5;)和强制转换(范围大的赋值给范围小的,如int i = (int)3.14)。多态的转型分为向上转型(自动转换)与向下转型(强制转换)。

7.2 向上转型(自动转换)

向上转型是子类类型向父类类型自动转换的过程,当父类引用指向子类对象时即发生向上转型,格式为:

父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();

原因在于父类类型范围相对子类较大,例如Animal包含所有动物,而Cat只是其中一种,因此子类可自动转型给父类类型变量。

7.3 向下转型(强制转换)

向下转型是父类类型向子类类型的强制转换过程。对于已向上转型的子类对象,将父类引用转为子类引用需使用强制类型转换格式,即:

子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();Cat c = (Cat) a;  

7.4 案例演示

为调用子类特有方法,需进行向下转型。以如下代码为例:
定义类:

abstract class Animal {  abstract void eat();  
}  class Cat extends Animal {  public void eat() {  System.out.println("吃鱼");  }  public void catchMouse() {  System.out.println("抓老鼠");  }  
}  class Dog extends Animal {  public void eat() {  System.out.println("吃骨头");  }  public void watchHouse() {  System.out.println("看家");  }  
}

定义测试类:

public class Test {public static void main(String[] args) {// 向上转型  Animal a = new Cat();  a.eat(); 				// 调用的是 Cat 的 eat// 向下转型  Cat c = (Cat)a;       c.catchMouse(); 		// 调用的是 Cat 的 catchMouse}  
}

7.5 转型的异常

转型过程中可能出现问题,如下代码虽能编译通过,但运行时会抛出ClassCastException类型转换异常:

public class Test {public static void main(String[] args) {// 向上转型  Animal a = new Cat();  a.eat();               // 调用的是 Cat 的 eat// 向下转型  Dog d = (Dog)a;       d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】}  
}

这是因为创建的是Cat类型对象,运行时无法转换为Dog对象。

7.6 instanceof关键字

为避免ClassCastException异常(ClassCastException 是 Java 中的一个运行时异常,当你尝试将一个对象强制转换为不兼容的类型时就会抛出该异常。),Java提供instanceof关键字用于校验引用变量类型,格式为:

变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false

因此,转换前可先进行判断,代码如下:

public class Test {public static void main(String[] args) {// 向上转型  Animal a = new Cat();  a.eat();               // 调用的是 Cat 的 eat// 向下转型  if (a instanceof Cat){Cat c = (Cat)a;       c.catchMouse();        // 调用的是 Cat 的 catchMouse} else if (a instanceof Dog){Dog d = (Dog)a;       d.watchHouse();       // 调用的是 Dog 的 watchHouse}}  
}

7.7 instanceof新特性

JDK14引入新特性,可将判断和强转合并为一行:

// 新特性
// 先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
// 如果不是,则不强转,结果直接是false
if(a instanceof Dog d){d.lookHome();
} else if(a instanceof Cat c){c.catchMouse();
} else{System.out.println("没有这个类型,无法转换");
}

八、综合练习

8.1 需求

  1. 定义狗类:包含年龄、颜色属性,以及eat(String something)(表示吃的东西)和看家lookHome(无参数)方法。
  2. 定义猫类:包含年龄、颜色属性,以及eat(String something)逮老鼠catchMouse(无参数)方法。
  3. 定义Person类(饲养员):包含姓名、年龄属性,以及keepPet(Dog dog, String something)(喂养宠物狗)和keepPet(Cat cat, String something)(喂养宠物猫)方法,生成空参、有参构造及setget方法。
  4. 定义测试类:实现keepPet(Dog dog, String something)方法打印“年龄为30岁的老王养了一只黑颜色的2岁的狗,2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃”;keepPet(Cat cat, String something)方法打印“年龄为25岁的老李养了一只灰颜色的3岁的猫,3岁的灰颜色的猫眯着眼睛侧着头吃鱼”。
  5. 思考
    • DogCat均为Animal的子类,现有针对不同动物定义不同keepPet方法的方式较为繁琐,思考如何简化及体会简化后的好处。
    • 尽管DogCatAnimal的子类,但都有特有方法,思考如何在keepPet中调用这些特有方法。

8.2 代码示例

// 动物类(父类)
public class Animal {private int age;private String color;public Animal() {}public Animal(int age, String color) {this.age = age;this.color = color;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public void eat(String something) {System.out.println("动物在吃" + something);}
}// 猫类(子类)
public class Cat extends Animal {public Cat() {}public Cat(int age, String color) {super(age, color);}@Overridepublic void eat(String something) {System.out.println(getAge() + "岁的" + getColor() + "颜色的猫眯着眼睛侧着头吃" + something);}public void catchMouse() {System.out.println("猫抓老鼠");}}// 狗类(子类)
public class Dog extends Animal {public Dog() {}public Dog(int age, String color) {super(age, color);}// 行为// eat(String something)(something表示吃的东西)// 看家lookHome方法(无参数)@Overridepublic void eat(String something) {System.out.println(getAge() + "岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");}public void lookHome() {System.out.println("狗在看家");}
}// 饲养员类
public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}// 饲养狗/*public void keepPet(Dog dog, String something) {System.out.println("年龄为" + age + "岁的" + name + "养了一只" + dog.getColor() + "颜色的" + dog.getAge() + "岁的狗");dog.eat(something);}// 饲养猫public void keepPet(Cat cat, String something) {System.out.println("年龄为" + age + "岁的" + name + "养了一只" + cat.getColor() + "颜色的" + cat.getAge() + "岁的猫");cat.eat(something);}*/// 想要一个方法,能接收所有的动物,包括猫,包括狗// 方法的形参:可以写这些类的父类 Animalpublic void keepPet(Animal a, String something) {if(a instanceof Dog d) {System.out.println("年龄为" + age + "岁的" + name + "养了一只" + a.getColor() + "颜色的" + a.getAge() + "岁的狗");d.eat(something);} else if(a instanceof Cat c) {System.out.println("年龄为" + age + "岁的" + name + "养了一只" + c.getColor() + "颜色的" + c.getAge() + "岁的猫");c.eat(something);} else {System.out.println("没有这种动物");}}
}// 测试类
public class Test {public static void main(String[] args) {// 创建对象并调用方法/*Person p1 = new Person("老王",30);Dog d = new Dog(2,"黑");p1.keepPet(d,"骨头");Person p2 = new Person("老李",25);Cat c = new Cat(3,"灰");p2.keepPet(c,"鱼");*/// 创建饲养员的对象Person p = new Person("老王",30);Dog d = new Dog(2,"黑");Cat c = new Cat(3,"灰");p.keepPet(d,"骨头");p.keepPet(c,"鱼");}
}
http://www.xdnf.cn/news/20341.html

相关文章:

  • CSS伪类
  • CSS 文件格式
  • 期货交易躲过AI捕杀—期货反向跟单策略
  • 基于PySide6与pyCATIA的圆柱体特征生成工具开发实战——NX建模之圆柱命令的参考与移植
  • 守护进程编程、GDB调试以及外网连接树莓派
  • 【数据结构】深入理解:完全二叉树中叶子节点与分支节点的数量关系推导
  • 每天学一个 Linux 命令(21):tree
  • Harmony5.0 设置应用全屏模式,隐藏导航栏和状态栏
  • 我的创作纪念日
  • HCIP-H12-821 核心知识梳理 (3)
  • 系统架构设计师:计算机组成与体系结构(如CPU、存储系统、I/O系统)高效记忆要点、知识体系、考点详解、、练习题并提供答案与解析
  • 4.3 熟悉字符串处理函数
  • 告别Feign:基于Spring 6.1 RestClient构建高可用声明式HTTP客户端
  • aop原理及场景
  • AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年4月18日第56弹
  • 如何通过OTP动态口令登录Windows操作系统实现安全管控?安当SLA双因素认证的行业化解决方案
  • 《P2882 [USACO07MAR] Face The Right Way G》
  • AI Agent智能体是什么?如何使用?
  • Django 结合 Vue 实现简单管理系统的详解
  • vue3+axios下载哪后端返回错误信息并动态提示
  • 【学习笔记】Py网络爬虫学习记录(更新中)
  • thinkphp实现图像验证码
  • 2025年03月中国电子学会青少年软件编程(Python)等级考试试卷(一级)真题
  • DDS Discovery数据
  • PM2模块
  • AI专题(一)----NLP2SQL探索以及解决方案
  • std::unordered_set(C++)
  • Java课程内容大纲(附重点与考试方向)
  • 算法01-最小生成树prim算法
  • C语言复习笔记--字符函数和字符串函数(上)