【Java 基础】:三大特征之多态

news2024/9/19 16:25:39

✨                                                 杏花疏影里,吹笛到天明       🌏 

📃个人主页:island1314

🔥个人专栏:java学习

⛺️  欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞


🚀引言 

在前两篇博客中,我们已经讲完了面向对象程序三大特性之一的封装、继承,

【Java 基础】类和对象(构造&this&封装&static&代码块)-CSDN博客

【Java 基础】三大特征之继承-CSDN博客

下面让我们来看看多态有哪些内容吧

1.  多态概念

💢💢在Java中,多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一方法进行不同的实现。具体来说,多态性指的是通过父类的引用变量来引用子类的对象,从而实现对不同对象的统一操作

  • 多态是方法或对象具有多种形态,是面向对象的第三大特征
  • 多态前提是两个对象(类)存在继承关系,多态是建立在封装和继承基础之上的。

2. 多态实现条件

在Java中,要实现多态性,就必须满足以下条件:

  1. 继承关系
    存在继承关系的类之间才能够使用多态性。多态性通常通过一个父类用变量引用子类对象来实现。

  2. 方法重写
    子类必须重写(Override)父类的方法。通过在子类中重新定义和实现父类的方法,可以根据子类的特点行为改变这个方法的行为,如猫和狗吃东西的独特行为。

  3. 父类引用指向子类对象
    使用父类的引用变量来引用子类对象。这样可以实现对不同类型的对象的统一操作,而具体调用哪个子类的方法会在运行时多态决定

例如,下面的案例是根据猫和狗吃东西动作的不同,而实现的多态:

class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}
class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃狗粮");
    }
}
class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃猫粮");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal1 = new Dog(); // 父类引用指向子类对象
        Animal animal2 = new Cat(); // 父类引用指向子类对象
        animal1.eat(); // 输出:狗吃狗粮
        animal2.eat(); // 输出:猫吃猫粮
    }
}

上面代码中涉及的 Override 叫作 重写

3. 重写

3.1 重写概念

💢💢重写(override):也称为覆盖。是 子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

想要理解方法重写,需要知道以下概念:

  1. 继承关系
    重写方法是基于父类和子类之间的继承关系。子类继承了父类的方法,包括方法的名称、参数列表和返回类型。

  2. 方法签名
    重写的方法与父类的方法具有相同的方法签名,即方法的名称、参数列表和返回类型必须一致(当然,如果返回类型的对象本身的类型则可以不同,但是必须要有继承关系)。方法签名不包括方法体。

  3. @Override注解
    为了明确表明这是一个重写的方法,可以使用 @Override 注解来标记子类中的方法。该注解会在编译时检查是否满足重写条件,如果不满足会报错。

  4. 动态绑定
    通过父类引用变量调用被子类重写的方法时,会根据实际引用的对象类型,在运行时动态绑定到相应的子类方法。(下面我们会进行详细讲解)

3.2 方法重写的规则

  1. 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  2. 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  3. 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected。
  4. 父类被static、private修饰的方法、构造方法都不能被重写。
  5. 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了, 那么如果此时编译器没有在发现父类中找到该方法, 那么就会编译报错, 提示无法构成重写

注:子类中重写的方法可以调用父类中被重写的方法,使用 super 关键字。

3.3 重写和重载的区别

首先回顾重载的实现条件:

  1. 方法名称相同:重载的方法必须具有相同的名称。
  2. 参数列表不同:重载的方法的参数列表必须不同。参数列表可以通过参数的类型、个数或顺序的不同来区分重载方法
  3. 返回类型可以相同也可以不同:重载的方法可以具有相同的返回类型,也可以具有不同的返回类型。返回类型不是重载方法的区分标准。
  4. 方法所在的类中:重载方法必须定义在同一个类中
  5. 方法的访问修饰符和异常:重载方法可以具有相同的访问修饰符(如 publicprivateprotected)和抛出的异常。
区别点重写重载
定义位置定义在父类和子类之间定义在同一个类中
方法签名重写方法具有相同的名称和方法签名重载方法具有相同的名称,但方法签名(参数类型和个数)不同

继承关系

是在子类中对父类方法的重新定义和实现不涉及继承关系,可以在同一个类中定义
运行时调用是根据对象的实际类型进行动态绑定在运行时确定是根据方法的参数列表的不同进行静态绑定在编译时确定
目的用于子类重新定义父类方法的行为,以适应子类的特定需求用于在同一个类中实现相似功能但具有不同参数的方法

3.4 重写的设计原则

☘️对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容

🍀静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

🌿动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

  • 当调用对象方法的时候,该方法会和该对象的运行类型绑定
  • 当调用对象属性时,没有动态绑定机制,即哪里声明,哪里使用。
  • 代码示例
    //父类
    class Person {
    	public void mission() {	
    		System.out.println("人要好好活着!");
    	}
    }
    
    //子类
    class Student extends Person {
    	@Override
    	public void mission() {	
    		System.out.println("学生要好好学习!");
    	}
    }
    
    //演示动态绑定
    public class DynamicBinding {
    	public static void main(String[] args) {
    		//向上转型(自动类型转换)
    		//程序在编译阶段只知道 p1 是 Person 类型
    		//程序在运行的时候才知道堆中实际的对象是 Student 类型	
    		Person p1 = new Student();  
    		
    		//程序在编译时 p1 被编译器看作 Person 类型
    		//因此编译阶段只能调用 Person 类型中定义的方法
    		//在编译阶段,p1 引用绑定的是 Person 类型中定义的 mission 方法(静态绑定)
    		//程序在运行的时候,堆中的对象实际是一个 Student 类型,而 Student 类已经重写了 mission 方法
    		//因此程序在运行阶段对象中绑定的方法是 Student 类中的 mission 方法(动态绑定)
    		p1.mission();
    	}
    }
    
    
    /* 结果输出
     *   学生要好好学习! 
     * */

🍎🍎总的来说:重载是在同一个类中根据参数列表的不同定义多个具有相同名称但参数不同的方法,而重写是子类重新定义和实现了从父类继承的方法。重载方法通过静态绑定在编译时确定调用,重写方法通过动态绑定在运行时确定调用。重载用于实现相似功能但具有不同参数的方法,重写用于改变父类方法的行为以适应子类的需求。

4. 多态的转型

4.1 向上转型

(1)本质父类的引用指向子类的对象

(2)特点

  1. 编译类型看左边,运行类型看右边
  2. 可以调用父类的所有成员(需遵守访问权限)
  3. 不能调用子类的特有成员
  4. 运行效果看子类的具体实现

(3)语法:父类类型 对象名 = new 子类类型()

(4)样例代码:

class A1{
    public String name;
    public int age;

    public A1(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(this.name + " 父类");
    }
    public void bark1(){
        System.out.println(this.name + " 不构成重写父类调用");
    }
}

class A2 extends A1 {
    private int num = 1;

    public A2(String name, int age){
        super(name,age);
    }
    public void eat(){ //和父类构成 重写
        System.out.println(this.name + " 子类");
    }
    public void bark2(){
        System.out.println(this.name + " 不构成重写子类调用");
    }
}

public class Test {
    public static void func1(A1 a) {
        a.eat();
        a.bark1();
    }
    public static A1 func2() {
        A1 a = new A2("change",18);
        return a;
    }

    public static void main(String[] args) {
        // 向上转型
        // 1. 直接赋值
        A2 a2 = new A2("change", 18);
        A1 a1 = a2; // 子类 A2 这个引用父类这个引用所指向对象
        // 上下两种相同
        A1 a = new A2("change", 18);
        a.eat(); // 调用的是 子类 中的 eat() 方法
        //a.bark2(); // 错误:无法访问 子类中独有的方法

        // 2,方法的参数,传参的时候进行向上转型
        // 传的是子类时,只能调用子类的方法
        //func1(a);
        func1(a2);

        // 3. 返回值向上转型
        func2();
    }
}

/* 结果输出
*   change 子类
*   change 子类
*   change 不构成重写父类调用
* */

【优缺点】

优点:让代码实现更简单灵活。
缺陷:不能调用到子类特有的方法。

4.2 向下转型

(1)本质一个已经向上转型的子类对象,将父类引用转为子类引用

(2)特点

  1. 只能强制转换父类的引用,不能强制转换父类的对象
  2. 要求父类的引用必须指向的是当前目标类型的对象
  3. 当向下转型后,可以调用子类类型中所有的成员

(3)语法:子类类型 引用名 = (子类类型) 父类引用;

(4)样例代码:

class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}
class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
    public void bark() {
        System.out.println("Dog is barking");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat is eating");
    }
    public void bark() {
        System.out.println("Cat is barking");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog();  // 向上转型
        animal.eat();  // 调用的是 Dog 类中的 eat() 方法
        // animal.bark();  // 错误:无法访问 Dog 类中独有的方法

        Dog dog = (Dog) animal;  // 向下转型
        dog.bark();  // 调用 Dog 类中的 bark() 方法
    }
}

/* 结果输出
 *   Dog is eating
 *   Dog is barking
 * */

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。 比如我们在上面代码进行一些修改:

 解释:这段代码在运行时出现了 ClassCastException 类型转换异常原因是 Dog 类与 Cat 类 没有继承关系,因此所创建的是 Dog 类型对象在运行时不能转换成 Cat 类型对象。

4.3 instanceof 关键字

因此Java中为了避免上述类型转换异常的问题,提高向下转型的安全性,引入了 instanceof 比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类型,如果该表达式为true,则可以安全转换。 

  • 格式:对象 instanceof 类名称
  • 解释:这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例
  • 代码示例 :
class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}
class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
    public void bark() {
        System.out.println("Dog is barking");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat is eating");
    }
    public void bark() {
        System.out.println("Cat is barking");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog();  // 向上转型

        if(animal instanceof Dog){ //判断对象 animal 是否是 Dog 类 的实例
            Dog dog = (Dog) animal;  // 向下转型
            dog.bark();  // 调用 Dog 类中的 bark() 方法
            //上面这两句也可简写为 ((Dog) animal).bark();
        }
        else if(animal instanceof  Cat){ //判断对象 animal 是否是 Cat 类 的实例
            ((Cat)animal).bark();
        }

    }
}

5. 多态的优缺点及应用

5.1 多态的优缺点

【使用多态的好处】

  1. 灵活性和可扩展性:多态性使得代码具有更高的灵活性和可扩展性。通过使用父类类型的引用变量,可以以统一的方式处理不同类型的对象,无需针对每个具体的子类编写特定的代码。
  2. 代码复用:多态性可以促进代码的复用。可以将通用的操作定义在父类中,然后由子类继承并重写这些操作。这样一来,多个子类可以共享相同的代码逻辑,减少了重复编写代码的工作量。
  3. 可替换性:多态性允许将一个对象替换为其子类的对象,而不会影响程序的其他部分。这种可替换性使得系统更加灵活和可维护,可以方便地添加新的子类或修改现有的子类,而无需修改使用父类的代码。

  4. 代码扩展性:通过引入新的子类,可以扩展现有的代码功能,而无需修改现有的代码。这种可扩展性使得系统在需求变化时更加容易适应和扩展。

【使用多态的缺陷】

  1. 运行时性能损失:多态性需要在运行时进行方法的动态绑定,这会带来一定的性能损失。相比于直接调用具体的子类方法,多态性需要在运行时确定要调用的方法,导致额外的开销。
  2. 代码可读性下降:多态性使得代码的行为变得更加动态和不确定。在某些情况下,可能需要跟踪代码中使用的对象类型和具体的方法实现,这可能降低代码的可读性和理解性。
  3. 限制访问子类特有成员:通过父类类型的引用变量,只能访问父类及其继承的成员,无法直接访问子类特有的成员。如果需要访问子类特有的成员,就需要进行向下转型操作,这增加了代码的复杂性和维护的难度。

虽然多态性具有一些缺点,但在大多数情况下,其优点远远超过缺点,使得代码更具灵活性、可扩展性和可维护性。因此,多态性在Java编程中被广泛应用。

5.2 多态的应用

🥝多态数组

多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。
代码示例:(循环调用基类对象,访问不同派生类的方法

class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}
class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat is eating");
    }
}
public class Test{

    public static void main(String[] args) {
        Animal[] animals = {new Animal(),new Dog(),new Cat()};
        for(Animal a: animals){a.eat();};
    }
}


/* 结果输出
* Animal is eating.
* Dog is eating
* Cat is eating
* */

6. 注意事项

我们需要避免在构造方法中调用重写的方法,先来看一段代码:

class B {
    public B() {
        // do nothing
        func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}

class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
}

public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
}

上面这段代码的运行结果是:D.func 0,其原因如下:

  1. 构造 D 对象的同时, 会调用 B 的构造方法。
  2. B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func 方法。
  3. 此时 D 对象自身还没有构造,因此 num 处在未初始化的状态,其值为 0。 如果具备多态性,num的值则应该是1。
  4. 所以在构造函数内,尽量避免使用实例方法,除了 final 和 private 方法。

结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题


💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心

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

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

相关文章

JVM参数有哪些?

JVM调优主要是通过定制JVM参数来提Java应用程序的运行速度 JVM参数大致可以分为三类: 1、标准指令:-开头,这些是所有的HotSPot都支持的参数。可以用java -help打印出来; 2、非标准指令:-X开头…

Mysql高级篇(上)—— Mysql架构介绍(二)

Mysql高级篇(上) MySQL架构介绍(二)逻辑架构逻辑架构剖析MySQL8.0中SQL执行流程Linux环境下MySQL8.0中SQL执行原理MySQL语法顺序Oracle中SQL执行流程(了解)数据库缓存池 buffer pool(了解&#…

86、pod部署策略

一、集群的调度 集群的调度: 怎么把pod部署到节点的方法。 1.1、调度的过程: scheduler是集群的调度器,主要任务就是把pod部署到节点上。 1.2、自动调度: 1、公平,保证每个可用的节点都可以部署pod 2、资源的高…

什么是工控安全,产线工控安全加固的方法

一、工控安全概述 想要了解工控安全,首先要了解其资产对象本身,也就是工业控制系统。 1、什么是工控 关于工业控制系统的定义,网上有很多,我就不再赘述,下面这张图是我从csdn找到的,个人觉得还不错。对照…

【多线程】CountDownLatch的简单实现

通过上一篇对CountDownLatch的使用,我们也明白了他的基本原理,接下来我们一起来实现一个CountDownLatch的基础效果 新建一个抽象类,包含countDownLatch需要的参数和方法 package com.atguigu.signcenter.nosafe.chouxiang;/*** author: jd* …

k8s集群的调度

目录 自动调度的原则 调度约束机制:list-watch apiserver和组件之间的watch机制 调度过程的默认算法 1.预算策略 预算的算法 2.优选策略 优选的算法 *用户定制节点部署 1.强制性节点部署 2.节点标签部署(匹配机制) 标签的增删改查…

selenium无法定位元素的几种解决方案

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 1、frame/iframe表单嵌套 WebDriver只能在一个页面上对元素识别与定位,对于frame/iframe表单内嵌的页面元素无法直接定位。 解决方法: d…

Hadoop 下载

下载法一:官方下载 hadoop官网 1.选择要下载的版本,这里我以3.4.0为例进行说明; 2.跳转后,选择对应系统架构的,进行下载; 下载法二:国内镜像源下载 1.阿里云 这里我以mac m1为案例&#x…

【已解决】Halcon错误代码4104

问题描述 在运行的时候发现报错4104,如下图所示 解决 从字面上理解就是超出了计算机的内存空间了,那么着手解决的话就需要把没次训练的张数减少一些即可。去相对应的修改batch_size即可。 结果 如下图所示,正常在使用GPU训练。 搞定&…

Xilinx FFT IP使用

简介 本章节主要介绍FFT原理,以及Xilinx的FFT IP使用说明做详细介绍。 FFT介绍 FFT主要是将时域信号转换成频域信号,转换后的信号更方便分析。首先,FFT是离散傅立叶变换 (DFT) 的快速算法,那么说到FFT,我们自然要先讲清楚傅立叶变换。先来看看傅立叶变换是从哪里来的? 傅…

虚拟机无法打开网卡 service network restart失败

问题描述 命令重启网卡服务,结果报错如下:Job for network.service failed because the control process exited with error code. See "systemctl status network.service" and "journalctl -xe" for details 根据提示&#xff…

Hi3061M开发板初测——点亮小灯

目录 前言环境配置点亮led源码IDA集成了串口监视器最后下载到开发板中运行 前言 海思MCU体验官活动,Hi3061M开发板到手后,配置环境初步测试点亮小灯。 环境配置 环境配置按照gitee提供的redeme一步一步来配置起来很顺利。具体可自行查阅:环境…

文件包含漏洞PHP伪协议利用方法

使用php伪协议的前提 php.ini中的allow_url_fopen 、allow_url_include这两个参数需要修改为on 1.file协议 使用file协议读取Windows的系统文件 http://127.0.0.1/123.php?cmdfile:///c:/windows/system32/drivers/etc/hosts 2.php://filter协议 使用php://filter协议可以…

JsonCpp库的使用

目录 一、Json数据格式 二、JsonCpp介绍 2.1 Json::Value 2.2 序列化接口 2.3 反序列化接口 三、JsonCpp的使用 3.1 头文件包含 3.2 序列化 3.3 反序列化 四、补充 Json 是⼀种数据交换格式,它采⽤完全独⽴于编程语⾔的⽂本格式来存储和表示数据。 一、J…

P01-Java何谓数组

P01-Java何谓数组 一、数组声明创建 1.1 数组声明的语法 与c有所不同 在Java中,数组声明语法 首选语法: //数据类型[] 数组名称; int[] arr;次选,与c类似 //数据类型 数组名称[]; int arr[];1.2 数组创建语法 与c指针有所相似&#xff0…

嵌入式开发与应用按键外部中断实验二

一、实验目的 1. 通过实验掌握中断式键盘控制与设计方法; 2. 熟悉编写中断服务程序; 3. 掌握中断系统相关知识,掌握外部中断应用方法和处理过程; 4. 掌握实验处理器中断处理的软件编程方法; 5. 从按键程序的设计对系统的编程…

Prometheus_0基础_学习笔记

一、基本概念 Prometheus是由golang语言开发的一套开源的监控、报警、时间序列数据库的组合,是一款基于时序数据库的开源监控告警系统。 时间序列数据库:时间序列数据库(Time Serires Database , TSDB)不同于传统的关系型数据库。…

是缔造AI程序员,还是AI缔造程序员?

随着AIGC(如ChatGPT、MidJourney、Claude等)大语言模型的涌现,AI辅助编程工具的普及使得程序员的工作方式正在发生深刻变革。AI的迅猛发展,不仅提升了编程效率,也引发了对程序员未来职业前景的深思。我们应当如何在这一…

【归并分而治之】逆序对的应对之策

目录 1.前言2.题目简介3.求解思路为什么要这样做?快在哪?为什么这种方法会想到结合归并排序?如何在一左一右中统计剩下的逆序对个数?固定右边的数,用降序会怎么样???思路的本质是巧妙…

【MySQL07】【锁】

文章目录 一、前言二、事务的读写情况1. 写-写情况2. 读-写情况3. 一致性读4. 锁定读2.1 共享锁和独占锁2.2 锁定读的语句 5. 写操作 三、多粒度锁四、表锁和行锁1. 表级锁1.1 表级别的 S锁 和 X锁1.2 表级别的 IS 锁和 IX锁1.3 表级别的 AUTO-INC 锁 2. 行级锁2.1 行级锁的分类…