Java基础38 面向对象三大特征之多态

news2025/1/18 6:55:10

OOP之多态

  • 多态
    • 1.多态的具体体现
    • 2.向上转型
    • 3.向下转型
    • 4.属性重写
    • 5.instanceOf
    • 6.动态绑定机制(核心)
      • ● Java的动态访问机制
    • 7.多态数组
    • 8.多态参数

)


多态

● 多【多种】态【状态】

方法或对象具有多种形态。 是建立在封装和继承之上的面向对象的第三大特征。

1.多态的具体体现

  1. 方法的多态
    重写和重载体现多态

  2. 对象的多态(核心)
    (1)一个对象的编译类型和运行类型可以不一致。
    (2)编译类型在定义对象时,就确定了,不能改变。
    (3)运行类型是可以变化的。
    (4)编译类型看定义时 “=” 号 的左边,运行看 “=” 号的右边

例如:
Animal animal = new Dog();
【这里animal编译类型是Animal,运行类型是Dog】
animal = new Cat();
【animal的运行类型变成了Cat,编译类型仍然是Animal】

2.向上转型

○ 多态的前提是:两个对象(类)存在继承关系。

多态的向上转型

  1. 本质:父类的引用指向了子类的对象

  2. 语法:父类类型 引用名 = new 子类类型();

  3. 特点:

    • 编译类型看左边,运行类型看右边。
    • 可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;
    • 最终运行效果看子类的具体实现。

向上转型调用方法的规则
(1)可以调用父类中的所有成员(需遵守访问权限)。
(2)但是不能调用子类的特有成员。
(3)因为在编译阶段,能调用哪些成员,是由编译类型来决定的,最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,规则与方法调用规则一致。

以下面例子为例说明向上转型的规则

Animal类

class Animal{
		String name = "动物";
		int age = 10;
		public void sleep(){
				System.out.println("睡");
		}
		public void run(){
				System.out.println("跑");
		}
		public void eat(){
				System.out.println("吃");
		}
		public void show(){
				System.out.println("表演");
		}	
}

Cat类,继承Animal类

class Cat extends Animal {
		public void eat(){ //重写父类的eat
				System.out.println("猫吃鱼");
		}
		public void catchMouse(){ //cat的特有方法
				System.out.println("猫抓老鼠");
		}
}

Test类

public class Test{
	public static void mian(String[] args){
		//向上转型:父类的引用指向子类的对象
		//语法:父类类型引用名 = new 子类类型();
		Aniaml animal = new Cat();
		// Object obj = new Cat(); 也是可以的,因为Object也是Cat的父类
		
		//调用父类中的所有成员(需遵守访问权限)
		//不能调用子类的特有成员
		//animal.catchMouse(); 是错误的,因为catchMouse是Cat中的特有方法
		animal.eat(); 
		animal.run(); 
		animal.show();
		animal.sleep();
		
		System.out.println("out~");
	}
}

3.向下转型

多态的向下转型

  1. 语法:子类类型 引用名 = (子类类型) 父类引用
  2. 只能强转父类的引用,不能强转父类的对象
  3. 要求父类的引用必须指向的是当前目标类型的对象
  4. 可以调用子类类型中所有的成员

向下转型的使用

以Aniaml类与cat类为例,新增一个dog类

Dog类

public class Dog extends Animal{
}

在Test类中测试

调用Cat的catchMouse方法
//使用多态的向下转型
//(1)语法 : 子类类型 引用名 = (子类类型) 父类引用;
Cat cat = (Cat) animal; //cat的编译类型是Cat,运行类型是Cat
cat.catchMouse();
//(2)父类的引用必须指向的是当前目标类型的对象
//Dog dog = (Dog)animal; //这句会报错,提示类异常

4.属性重写

属性没有重写之说,属性的值看编译类型。

public class Test{
	public static void main(String[] args){
		//属性没有重写之说,属性的值看编译类型
		Base base = new Sub(); //向上转型
		System.out.println(base.count); //编译类型为base,则输出10
		Sub sub = new Sub();
		System.out.println(base.count); //编译类型为sub,则输出20
		
	}
}

class Base { //父类
	int count = 10; //属性
}

class Sub extends Base { //子类
	int count = 20; //属性
}

5.instanceOf

nstanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型。

public class Test{
	public static void main(String[] args){
		BB bb = new BB();
		System.out.println(bb instanceof BB);//true
		System.out.println(bb instanceof AA);//true

		AA aa = new BB();
		System.out.println(aa instanceof AA); //true
		System.out.println(aa instanceof BB); //true

		Object obj = new Object();
		System.out.println(obj instanceof AA);//false
		System.out.println(AA instanceof obj);//true

		String str = "yes";
		System.out.println(str instanceof Object);//true
	}
}

6.动态绑定机制(核心)

Java重要特征:动态绑定机制

现在有两个类,A类与B类,其中B类是A类的子类,在main方法中创建对象: A a = new B();调用a.sum()与a.sum1()看看分别得到什么结果?

A类

class A{ //父类
	public int i =10;
	public int sum(){
		return getI() + 10;
	}
	public int sum1(){
		return i + 10;
	}
	public int getI(){
		return i;
	}
}

B类

class B extends A{ //子类
	public int i = 20;
	public int sum(){
		return i + 20;
	}
	public int getI(){
		return i;
	}
	public int sum1(){
		return i + 10;
	}
}

在main方法中 A a = new B(); //向上转型 System.out.println(a.sum()); //输出40
System.out.println(a.sum1()); //输出30

现在注释掉B类中的sum方法,那输出的结果又该如何?

B类

class B extends A{ //子类
	public int i = 20;
	//public int sum(){
	//	return i + 20;
	//}
	public int getI(){
		return i;
	}
	public int sum1(){
		return i + 10;
	}
}

在main中调用
System.out.println(a.sum());

因为B类中的sum被注释了,所以会自动向上找sum方法,而父类A中恰好有sum方法,所以调用了A类的sum方法,而A类的sun方法里返回的是getI()+10;而A与B类中都有getI方法,这里就涉及到了java的动态访问机制

● Java的动态访问机制

  1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定。
  2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。【属性没有动态绑定机制】

现在回过头去看刚才的问题,刚才调用的getI()方法,是属于对象的方法,所以该方法会和该对象的内存地址/运行类型绑定,我们创建的时候使用的运行类型是B类型,所以getI应该调用B类的getI方法:return
i;所以最后a.sum()输出的结果为:20+10 = 30。

那如果再把B类中的sum1()方法再注删掉呢?a.sum1()的结果是什么?

A类

class A{ //父类
	public int i =10;
	public int sum(){
		return getI() + 10;
	}
	public int sum1(){
		return i + 10;
	}
	public int getI(){
		return i;
	}
}

B类

class B extends A{ //子类
	public int i = 20;
	public int getI(){
		return i;
	}
}

由于B中并没有sum1方法,所以依然调用父类A的sum1方法,而sum1()中的 i + 10;i为属性,前面介绍过,java动态绑定机制是:当调用对象的属性时,,没有绑定动态机制,就是哪里声明,哪里使用,所以直接返回B类中的i,结果为:10 + 10 = 20


7.多态数组

多态数组

数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。

应用实例:

现有一个继承结构如下:要求创建1个Person对象、2个Student对象和2个Teacher对象,统一放在数组中,并调用say方法。

代码实现

Person类

public class Person { //父类
    private String name;
    private int age;

    public String say(){ //返回名字和年龄
        return name + "\t" + age;
    }

    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;
    }
}

Student类

public class Student extends Person{

    private double score;

    //重写父类say
    public String say(){

        return "学生" + super.say() + "score =" + score;
    }

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }


    public double getScore() {

        return score;
    }

    public void setScore(double score) {

        this.score = score;
    }
}

Teacher类

public class Teacher extends Person{
    private double salary;

    //重写父类的say方法
    public String say(){

        return "老师 :" + super.say()+"salary = " + salary;
    }

    public Teacher(String name, int age, double salary) {
        super(name,age);
        this.salary = salary;
    }

    public double getSalary() {

        return salary;
    }

    public void setSalary(double salary) {

        this.salary = salary;
    }
}

Test类(主方法类)

创建多态数组并遍历调用say方法

public class test {
//    在main中,分别创建Person和Student对象,调用say方法输出自我介绍
    public static void main(String[] args) {

        //创建对象数组
        Person[] persons = new Person[5];
        persons[0] = new Person("jack",20);
        persons[1] = new Student("mary",18,100);
        persons[2] = new Student("smith",19,30.1);
        persons[3] = new Teacher("scott",30,20000);
        persons[4] = new Teacher("queen",29,25000);

        //循环遍历多态数组,调用say
        for(int i = 0; i < persons.length; i++){
            //这里person[i] 编译类型是Person,运行类型是根据实际情况由JVM来判断
            System.out.println(persons[i].say());//动态绑定机制
        }
    }
}

运行效果

在这里插入图片描述

现在升级需求:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study怎么调用?

含特有方法的Teacher

public class Teacher extends Person{
    private double salary;

    //重写父类的say方法
    public String say(){

        return "老师 :" + super.say()+"salary = " + salary;
    }

    //teacher特有方法
    public void teach(){
        System.out.println("老师:" +getName() + "正在授课");
    }

    public Teacher(String name, int age, double salary) {
        super(name,age);
        this.salary = salary;
    }

    public double getSalary() {

        return salary;
    }

    public void setSalary(double salary) {

        this.salary = salary;
    }
}

含特有方法的Student

public class Student extends Person{

    private double score;

    //重写父类say
    public String say(){

        return "学生" + super.say() + "score =" + score;
    }

    public void study(){
        System.out.println("学生:" + getName() + "正在上课");
    }

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }


    public double getScore() {

        return score;
    }

    public void setScore(double score) {

        this.score = score;
    }
}

Test类

通过向下转型与类型判断完成子类特有方法的调用。

public class test {
//    在main中,分别创建Person和Student对象,调用say方法输出自我介绍
    public static void main(String[] args) {

        //创建对象数组
        Person[] persons = new Person[5];
        persons[0] = new Person("jack",20);
        persons[1] = new Student("mary",18,100);
        persons[2] = new Student("smith",19,30.1);
        persons[3] = new Teacher("scott",30,20000);
        persons[4] = new Teacher("queen",29,25000);

        //循环遍历多态数组,调用say
        for(int i = 0; i < persons.length; i++){
            //这里person[i] 编译类型是Person,运行类型是根据实际情况由JVM来判断
            System.out.println(persons[i].say());//动态绑定机制
            //使用 类型判断 + 向下转型
            if(persons[i] instanceof Student){ //判断person[i] 的运行类型是不是Student
                Student student = (Student) persons[i];
                student.study();
                //熟悉后可以简化为一条语句:
                //((Student)persons[i]).study();
            } else if(persons[i] instanceof Teacher){
                Teacher teacher = (Teacher) persons[i];
                teacher.teach();
                //((Teacher)persons[i]).teach();
            } else if(persons[i] instanceof Person){

            } else{
                System.out.println("类型有误,请检查清楚!");
            }
        }
        }
    }

运行效果

在这里插入图片描述


8.多态参数

多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型。

应用案例

  • 定义员工类Employee,包含姓名和月工资【private】,以及计算年工资getAnnual的方法。普通员工和经理继承了员工。

  • 经理类多了奖金bonus属性和管理manage方法

  • 普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法。

  • 测试类中添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法【e.getAnnual()】

  • 测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法。

代码实现

员工类Employee

public class Employee {
    private String name;
    private double salary;

    public double getAnnual(){
        return 12 * salary;
    }

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

manage类

public class manger extends Employee{
    private double bonus;

    //获取年薪
    public double getAnnual(){
        return super.getAnnual() + bonus;
    }

    public void manage(){
        System.out.println("经理: "+ getName()+"is working");
    }

    public manger(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }
}

普通员工类(Ordinary_employees)

public class Ordinary_employees extends Employee{

    public void work(){
        System.out.println("普通员工 " +getName() +"is working");
    }

    public double getAnnual(){ //因为普通员工没有其他收入,则直接调用父类方法就可以
        return super.getAnnual();
    }

    public Ordinary_employees(String name,double salary){
        super(name,salary);
    }
}

test类

创建普通员工与经理的对象,添加两个新方法并输出需求。

public class test {
    //    在main中,分别创建Person和Student对象,调用say方法输出自我介绍
    public static void main(String[] args) {

        Ordinary_employees tom = new Ordinary_employees("tom",2500);
        manger milan = new manger("milan",5000,250000);
        test t = new test();
        t.showEmpAnnual(tom);
        t.showEmpAnnual(milan);
        t.testWork(tom);
        t.testWork(milan);
    }
    //添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法【e.getAnnual()】
    public void showEmpAnnual(Employee e){
        System.out.println(e.getAnnual());//动态绑定机制
    }
    //添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法。
    public void testWork(Employee e){
            if(e instanceof Ordinary_employees){
                ((Ordinary_employees)e).work();//向下转型
            }else if(e instanceof  manger){
                ((manger)e).manage(); //向下转型
            } else {

            }
    }

效果实现

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/18695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文带你学会Vue3基本语法

Vue3 起步1.通过 CDN 使用 Vue32.Vue3 模板语法文本Html属性表达式指令参数3.模板用户输入双向绑定1.通过 CDN 使用 Vue3 你可以借助 script 标签直接通过 CDN 来使用 Vue&#xff1a; <script src"https://unpkg.com/vuenext"></script>通过 CDN 使用…

在WPF中使用Prism弹出自定义窗体样式的对话框

概述 我们在Prism中弹出一个对话框&#xff0c;默认是一个Windows默认样式的窗口&#xff0c;这样会同自己所开发的项目完全不搭调&#xff0c;譬如下面这样子 那么如果为了配合软件主体的风格&#xff0c;可以做出类似这样效果 其实原理也很简单&#xff0c;Prism也考虑到了这…

不科学,RocketMQ生产者在一个应用服务竟然不能向多个NameServer发送消息

前言 目前有两套RocketMQ集群&#xff0c;集群A包含topic名称为cluster_A_topic&#xff0c;集群B包含topic名称为cluster_B_topic&#xff0c;在应用服务OrderApp上通过RocketMQ Client创建两个DefaultMQProducer实例发送消息给集群A和集群B&#xff0c;架构图如下&#xff1…

使用Vue脚手架配置代理服务器的两种方式

1 前言 本文主要介绍使用Vue脚手架配置代理服务器的两种方式 注意&#xff1a;Vue脚手架给我们提供了两种配置代理服务器的方式&#xff0c;各有千秋&#xff0c;使用的时候只能二选一&#xff0c;不能同时使用 2 代理 除了cros和jsonp&#xff0c;还有一种代理方式&#x…

传奇GM调整极品属性的命令------技术分享

传奇GM调整极品属性的命令 GM命令supermake命令用法&#xff01; 以下格式皆为supermake a b c   以上命令含义&#xff1a;调整A(装备)的B(属性)到C(点数) supermake 1 0 10  1代表武器  0代表攻击 10代表调整的点数 B参数代表需要调整的那项属性如攻击 魔法 道术 …

黑*头条_第4章_文章搜索前后端成形记 实名认证审核

黑*头条_第4章_文章搜索前后端成形记 & 实名认证审核 文章目录黑*头条_第4章_文章搜索前后端成形记 & 实名认证审核文章搜索前后端成形记 & admin实名认证审核1 文章详情-前端开发1.1登录接口1.1.1 基本定义1.1.2 code定义1.1.3 mapper实现1.1.4 service代码实现1.…

宝塔一键安装wordpress

使用宝塔面板来部署网站是非常方便的&#xff0c;以WordPress网站为例来说&#xff1a; 一般有两种方式安装WordPress网站&#xff0c;第一种是上传网站程序到网站根目录手动安装&#xff0c;另外一种是在宝塔面板后台左侧菜单&#xff0c;找到“WordPress一键部署”&#xff…

Map 和 Set

模型 一般我们把搜索的数据称为 关键字(key) , 关键字对应的值叫做 值(value) , 将之称为 key-value 键值对. 衍生出两种模型: 1. 纯 key 模型 例如 : 班级上点名, 在花名册上找人的名字. 2. key-value 模型 例如 : 统计一个字符串中每个字母出现的次数, 结果是每个字母和它对…

简易版 图书管理系统

目录 1. Book包 1.1 Book类 1.2 BookList类 2. User包 2.1 User抽象类 2.2 AdminUser类 2.3 NormalUser类 3. Operate包 3.1 MyOperate接口 3.2 AddOperation类 3.3 DelOperation类 3.4 ExitOperation 3.5 FindOperation类 3.6 ShowOperation类 3.7 BorrowedOpe…

MySQL——数据库、表的操作

文章目录数据库的操作创建数据库创建数据库例子字符集和校验规则查看数据库支持的字符集查看默认的字符校验规则校验规则对数据库的影响查看数据库显示详细的创建数据库语句修改数据库删除数据库查看连接情况表的操作创建表显示创建表的详细过程不同的数据库引擎查看表结构修改…

【C语言】操作符与优先级详解

C的操作符 文章目录C的操作符前言一、算术操作符二、移位操作符三、位操作符四、赋值操作符五、单目操作符六、条件操作符七、逻辑操作符八、条件操作符九、逗号表达式十、下标引用、函数调用和结构成员十一、表达式求值11.1 隐式类型转换12.2 算术转换12.2 操作符的属性总结前…

解决Vue前后端跨域问题的多种方式

1 前言 本文主要介绍借助解决Vue前后端跨域问题的几种方式 说到ajax请求&#xff0c;就不得不说下xhr(XMLHttpRequest)了&#xff0c;它可以说是鼻祖&#xff0c;但是实际开发中&#xff0c;我们不会直接使用它&#xff0c;而是进行二次封装或者使用成熟的第三方封装&#xf…

Zookeeper:分布式过程协同技术

Zookeeper 是一个高性能的分布式一致系统&#xff0c;在分布式系统中有着广泛的应用。基于它&#xff0c;可以实现诸如“分布式同步”、“配置管理”、“命名空间管理”等众多功能&#xff0c;是分布式系统中常见的基础系统。Zookeeper 主要用来解决分布式集群中应用系统的一致…

http,https,ip,tcp,udp

http:超文本传输协议&#xff0c;明文传输&#xff0c;不安全 超文本&#xff1a;早期&#xff0c;文本存在本地&#xff0c;文本可以被计算机解析为二进制的数据包&#xff0c;随着发展&#xff0c;出现图片&#xff0c;视频&#xff0c;链接等&#xff0c;成为超文本 传输&a…

批量生成Excel文件,可以按模板进行自动生成

目录 一、文件目录结构 二、编辑生成名单 三、编辑模板 四、生成操作 软件描述&#xff1a;根据Excel模板 和 生成名单 可以批量生成相同格式的文件&#xff0c;可以应用于考核、工资单等文件的批量生成。方便快捷&#xff0c;有需求的小伙伴可以到最下面点击下载 注&#…

mysql 客户端简单搭建

主要使用的是mysql开发包中的api接口 操作流程 1.初始化mysql操作句柄 MYSQL *mysql_init(MYSQL *mysql)&#xff1b; 对传人的句柄进行初始化 若传入的句柄为NULL&#xff0c;则内部会动态申请空间&#xff0c;进行初始化&#xff0c;并返回句柄首地址 返回值&#xff1a;若…

基于51单片机的ds18b20数字华氏温度计

资料编号&#xff1a;114 下面是相关功能视频演示&#xff1a; 114-基于51单片机的数字华氏温度计报警&#xff08;源码仿真全套资料&#xff09;功能讲解&#xff1a; 采用51单片机采集DS18B20的温度&#xff0c;LCD1602显示&#xff0c;并且可以设置上下限值&#xff0c;超…

Unity UI 框架

开源地址&#xff1a; GitHub - NRatel/NRFramework.UI: 基于 Unity UGUI 的 UI 开发框架基于 Unity UGUI 的 UI 开发框架. Contribute to NRatel/NRFramework.UI development by creating an account on GitHub.https://github.com/NRatel/NRFramework.UI 一、需求/功能要点…

headscale的部署方法和使用教程

headscale的部署方法和使用教程1. headscale文件下载2. 上传并赋予文件权限3. 创建以及修改相关配置文件3.1 创建配置目录&#xff1a;3.2 创建目录用来存储数据与证书&#xff1a;3.3 创建空的 SQLite 数据库文件&#xff1a;3.4 创建 Headscale 配置文件&#xff1a;3.5 创建…

Vue 组件间通信并不是每一次操作都会触发新的通信

需求&#xff1a;新增或者修改都需要组件间立马通信。 操作&#xff1a;把B组件(子组件&#xff0c;这里指的是三级联动组件)的数据传输过来&#xff0c;在A(父组件)组件中处理 即 子传父 这里指的是修改页面或者新增页面三级联动下拉选择完之后 点击 提交 会执行A组件的修改操…