JavaSE之多态

news2024/10/18 18:26:14

文章目录

  • 多态的概念
  • 多态的实现条件
  • 向上转型
  • 动态绑定
  • 静态绑定
  • 向下转型
  • Object类

        在这里插入图片描述

                                           给个关注叭
      在这里插入图片描述

  个人主页

  JavaSE专栏

前言:本篇文章主要整理了多态的概念、实现条件、多态性的体现、向上转型、向下转型、动态绑定和静态绑定以及Object类中的equals、toString、hashCode方法。

多态的概念

  通俗来说,就是多种形态;具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。比如动物之间存在多态,不同动物持不同的食物,狗吃狗粮,猫吃猫粮,当不同的动物执行吃的动作时,就会产生不同的结果,即狗吃狗粮,猫吃猫粮。结合实际代码更容易理解,实际代码示例如下:

class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(name + "正在吃饭...");
    }
}
class Dog extends Animal {
    public Dog (String name,int age) {
        super(name,age);
    }
    public void eat () {
        System.out.println(name + "正在吃狗粮...");
    }
}

class Cat extends Animal {
    public Cat (String name,int age) {
        super(name,age);
    }
    public void eat () {
        System.out.println(name + "正在吃猫粮...");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal dog = new Dog("旺财",2);
        Animal cat = new Cat("小花",1);
        func(dog);
        func(cat);
    }
    public static void func(Animal a) {
        a.eat();
    }
}

运行结果:
旺财正在吃狗粮…
小花正在吃猫粮…

根据结果说明,当一个引用 引用了不同的对象 调用同一个方法,所表现的形式却不一样,这就是多态。


多态的实现条件

  1. 必须在继承体系下
  2. 父类引用 引用了 子类的对象(向上转型)
  3. 子类必须重写父类中的方法
  4. 通过父类的引用 访问 子类重写的 这个方法
    【关于重写的解释,请移步 重写和重载的区别 】

要进一步理解多态性的体现,需要认识向上转型和动态绑定。如下

向上转型

向上转型就是父类引用 引用了 子类的对象。
向上转型的三种形式:

  1. 直接赋值,代码示例如下:
Animal animal = New Dog("旺财", 2);
Animal animal = New Cat("小花", 1);
  1. 作为方法的参数进行传递,代码示例如下:
    public static void main(String[] args) {
        Animal dog = new Dog("旺财",2);
        Animal cat = new Cat("小花",1);
        func(dog);
        func(cat);
    }
    public static void func(Animal a) {
        a.eat();
    }
  1. 作为返回值进行接收
public Animal func() {
    Dog dog = new Dog("旺财",2);
    return dog;
}

向上转型的优点:

  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else

什么叫 “圈复杂度” ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.
如果一个方法的圈复杂度太高,就需要考虑重构了。

  1. 可扩展能力更强

对于类的调用者来说, 只要创建一个新类的实例就可以了, 改动成本很低

向上转型的缺点:
不能通过父类的引用访问子类的成员,只能访问父类自己特有的成员

动态绑定

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

  1. 父类引用 引用了 子类对象
  2. 子类重写了父类的方法
  3. 通过父类引用调用这个被子类重写的方法,最终结果是 调用子类重写的方法。

以上三个阶段过程 就是动态绑定(运行时绑定)


代码示例如下:

class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(name + "正在吃饭...");
    }
}
class Dog extends Animal {
    public Dog (String name,int age) {
        super(name,age);
    }
    // 2.子类重写父类的eat方法
    public void eat () {
        System.out.println(name + "正在吃狗粮...");
    }
}
public class Test {
    public static void main(String[] args) {
        // 1.发生向上转型
        Animal animal = new Dog("旺财",2);
        animal.eat();//编译时是父类的eat,运行时调用子类eat,体现动态绑定
    }

运行结果:
旺财正在吃狗粮…


  • 在发生向上转型的情况下,如果通过父类的引用去访问子类的方法,是会报错的。即发生向上转型,父类引用只能访问自己的成员,不能访问子类的成员。这也是向上转型的缺点。
  • 但是在发生向上转型 并且 子类重写了父类的方法 这两种前提下,通过父类引用 是可以访问 这个被子类重写的方法的。在最新生成的编译好的字节码文件里,此时编译的确实是父类的方法。但是在运行时被动态地 绑定到了子类这个被重写的方法上。所以动态绑定也叫做运行时绑定。

静态绑定

  静态绑定也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表方法重载。
编译时确定调用哪个方法 在运行时就会调用哪个方法,不会改变。这是静态绑定。


向下转型

  将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。代码示例如下:

class Animal {
    public String name;
    public int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(name + "正在吃饭...");
    }
}
class Dog extends Animal {
    public Dog (String name,int age) {
        super(name,age);
    }
    public void eat () {
        System.out.println(name + "正在吃狗粮...");
    }
    public void bark() {
        System.out.println(name + "正在汪汪叫...");
    }

class Bird extends Animal {
    public Bird (String name,int age) {
        super(name,age);
    }
    public void eat () {
        System.out.println(name + "正在吃鸟粮...");
    }
    public void fly() {
        System.out.println(name + "正在飞...");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal1 = new Dog("旺财",2);
        func(animal1);
        Animal animal2 = new Bird("小花",1);
        func(animal2);
        //animal1.bark();  编译错误,animal1不能访问子类特有的方法
        //向下转型
        Dog dog = (Dog)animal1;
        dog.bark();//编译成功并成功运行
    }
    public static void func(Animal a) {
        a.eat();
    }
}

但是,向下转型 不安全,代码示例如下:

   Animal animal1 = new Dog("旺财",2);
   func(animal1);
   Animal animal2 = new Bird("小花",1);
   func(animal2);
   //animal1.bark();  编译错误,animal1不能访问子类特有的方法
   //向下转型
   Dog dog = (Dog)animal1;
   dog.bark();//编译成功并成功运行
   Bird bird = (Bird)animal1;
   //bird.fly();  编译不成功
   

此时,会报以下错误


animal1向下转型为Dog后,再调用子类Dog特有的bark方法,能够成功编译并运行。而animal1再次向下转型为Bird后,再次调用子类Bird中特有的fly方法时,虽然编译能通过但运行时会报错,这是因为向下转型时,animal1引用指向的正是转换之后的Dog类的对象,因此dog.bark()能成功运行;而将animal1向下转型为Bird类时,因为此时animal1这个引用指向的是Dog类的对象,并不是发生转型后的Bird类的对象,所以运行时会报错。

为了避免这种不安全性,我们使用instanceof关键字,代码示例如下:

      Animal animal1 = new Dog("旺财",2);
      Animal animal2 = new Bird("小花",1);
      //向下转型
      if(animal1 instanceof Dog) {
          Dog dog = (Dog)animal1;
          dog.bark();//编译成功并成功运行
       }else {
          System.out.println("animal没有引用Dog实例");
       }
       if(animal1 instanceof Bird) {
           Bird bird = (Bird)animal1;
           bird.fly();
       }else {
          System.out.println("animal没有引用Bird实例");
       }

Object类

  Object类是所有类的父类,所以任何类都可以向Object类进行向上转型,即所有类的对象都可以使用Object的引用进行接收;Object类可以作为方法的参数接收任何类的对象,Object类是参数的最高统一类型。同时Object类中也存在一些已经定义好的方法,如下:在这里插入图片描述

所以说,Java中的每一个类只要根据需求重写了Object中的方法就可以使用我们重写的方法,这个过程涉及了刚刚所整理的动态绑定的知识,也就是我们任何一个子类和Object类都构成了继承关系,任何的类就会作为子类重写父类Object类中的方法,进而形成了动态绑定(运行时绑定)。
【这里穿插一个我当时学习时的疑惑点】就是动态绑定的前提不一定会存在向上转型,也就是说不存在向上转型,只要在继承关系上,子类重写了父类的方法,那么通过父类引用访问这个重写的方法时,也一定会调用子类中这个被重写的方法。
【这里再提一下关于继承中访问成员的知识点】不论是通过子类引用访问成员变量还是成员方法,都会遵循以下准则:

  1. 访问时父子类存在同名的成员变量,优先访问子类的变量
  2. 访问时父子类存在同名的方法,如果方法的参数列表相同(构成重写),不论是通过子类引用访问还是通过父类>引用访问都一定是访问子类中重写的这个方法;如果参数列表不同(构成重载),则是调用的具有相一致参数列表>的方法(根据参数列表进行匹配)
  3. 访问时父子类不存在相同名称的变量或方法,子类中有,优先访问子类的,子类中没有,到父类中找,父类也没有,则报错。

equals方法
  通过Object类的学习,我们在自定义的类中一般都要根据需要重写Object中的方法(当然是在用到这个方法前提下),比如说我们自定义了一个Person类,又实例化了两个对象,我们希望通过名字来进行比较他们是否同名同年龄,但是通过使用equals方法,只能比较这两个对象的引用是否相同(就是比较两个地址是否相同),显然没有达到需求。因为此时调用的是父类Object中的equals方法,而这个方法的底层就是使用 '='来比较两个对象的引用,即比较两个地址是否相同。代码示例如下:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test2 {
    public static void main(String[] args) {
        Person person1 = new Person("张三",18);
        Person person2 = new Person("张三",18);
        System.out.println(person1.equals(person2));
    }
}

运行结果:
  false

因此我们可以根据需求在Person类中重写父类Object中的equals方法,代码示例如下:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public boolean equals(Object obj) {
        Person tmp = (Person)obj;
        return this.name.equals(tmp.name) && this.age == tmp.age;
    }
}
public class Test2 {
    public static void main(String[] args) {
        Person person1 = new Person("张三",18);
        Person person2 = new Person("张三",18);
        System.out.println(person1.equals(person2));
    }
}

运行结果:
  true

  对equals方法进行重写,也可以让编译器自己生成,右键—>construct—>equals() and hashCode(),然后一直next
在这里插入图片描述
自动生成的重写的equals方法如下:

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

@Override 这个东西可以进行自动校验,检验你重写的方法名、参数列表、返回值是否相同,它会自动识别并提醒。
this == o是判断这两个引用是否相同,如果是同一个引用,那么一定相同,返回true
0 == null || getClass() != o.getClass()前者是判断另一个作为参数的引用是否为空,为空返回false;后者是判断进行比较的这两个引用的类型是否相同,类型不同返回false
前两个语句都不成立,则开始正式比较name和age是否都相同。

总结:比较对象中内容是否相同的时候,一定要重写equals方法。


toString方法
  toString方法也和equals方法一样,如果需要使用toString方法打印一个person实例中的成员变量,来获取对象信息,如果不重写toString方法,调用时也还是会调用Object中的toString方法,打印出这个引用的地址,代码示例如下:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test2 {
    public static void main(String[] args) {
        Person person1 = new Person("张三",18);
        System.out.println(person1.toString());
    }
}

运行结果:
demo.Person@1b28cdfa

所以要达到需求,同样也需要在Person类中重写toString方法,代码示例如下:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test2 {
    public static void main(String[] args) {
        Person person1 = new Person("张三",18);
        System.out.println(person1.toString());
    }
}

运行结果:
Person{name=‘张三’, age=18}

  同样对toString方法进行重写也可以使用编译器自动生成,右键—>construct—>toString()—>选择变量—>OK
在这里插入图片描述
自动生成的重写的equals方法如下:

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

同样@Override也是校验作用

总结:获取对象中内容信息的时候,一定要重写toString方法。


hashCode方法
  hashCode方法是来计算一个对象的具体位置,我们先假设它是一个内存地址。
我们假设名字相同、年龄相同的两个对象是储存在相同的内存地址,如果没有重写hashCode方法,那么在访问这个方法时就会调用Object中的这个方法,代码示例如下:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test2 {
    public static void main(String[] args) {
        Person person1 = new Person("张三",18);
        Person person2 = new Person("张三",18);
        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
    }
}

运行结果:
455659002
250421012
此时两个对象的hash值不一样

  根据结果说明,Object类中的这个hashCode方法不满足我们的要求,所以我们可以重写hashCode方法,来满足只要对象中的名字和年龄相同就是相同的地址,代码示例如下:

class Person {
    public String name;
    public int age;
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test2 {
    public static void main(String[] args) {
        Person person1 = new Person("张三",18);
        Person person2 = new Person("张三",18);
        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
    }

运行结果:
24022538
24022538
此时两个对象的hash值就一样了


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

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

相关文章

web网页---QQ注册页面的实现

代码部分 <title>QQ注册</title> </head> <style>.text-style1 {color: black;font-size: 28px;}.text-style2 {color: rgb(37, 37, 37);font-size: 17px;}.text-style3{color: rgba(189, 185, 185, 0.904);font-size: 9px;}.text-style4 {color: rg…

[枚举坤坤]二进制枚举

啊&#xff0c;哈喽&#xff0c;小伙伴们大家好。我是#Y清墨&#xff0c;今天呐&#xff0c;我要介绍的是二进制枚举。 简介 TA是枚举算法中的一种特例&#xff0c;其主要运用在求某一集合的子集个数这一算法中。其基本概念就是利用二进制数中的1与0代表选择与否&#xff0c;其…

gradle镜像下载地址

gradle镜像下载地址:Index of /gradle/https://mirrors.cloud.tencent.com/gradle/

MongoDB 的安装详情

在虚拟机里面opt下 新建一个mongodb文件夹 再新建一个opt/mongodb/data文件夹&#xff0c; 然后将挂载的mongodb数据放到data文件夹里&#xff1a; 【把mongodb的数据挂载出来&#xff0c;以后我们再次重启的时候 数据起码还会在】 冒号右边 挂载到左边的路径 docker run -…

vue npm run ...时 报错-系统找不到指定的路径

vue项目修改时&#xff0c;不知道那一步操作错误了&#xff0c;运行npm run …时报错 系统找不到指定的路径&#xff0c;对此进行记录一下&#xff01; 解决方法&#xff1a; 1、执行 npm install 命令&#xff0c;重新下载模块 2、根据下方提示执行 npm fund 查看详细信息 …

哪家云电脑便宜又好用?ToDesk云电脑、顺网云、达龙云全方位评测

陈老老老板&#x1f934; &#x1f9d9;‍♂️本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f9d9;‍♂️本文简述&#xff1a;讲一下市面上云电脑的对比。 &#x1f9d9;‍♂️上一篇文…

MySQL系列—14.锁

目录 1、锁 读-读情况 写-写情况 读-写或写-读情况 2、锁的分类 2.1 读锁、写锁 2.2 表级锁 2.2.1 表级的S锁/X锁 2.2.2 意向锁 2.2.3 元数据锁(MDL锁) 2.3 行级锁 2.3.1 记录锁&#xff08;Record Locks&#xff09; 2.3.2 间隙锁 2.3.3 临键锁&#xff08;Next…

(C/C++)文件

目录 1. 为什么使用文件 2. 什么是文件 2.1 程序文件 2.2 数据文件 3. 文件的打开和关闭 3.1 文件指针 3.2 文件的打开和关闭 4. 文件的顺序读写 fputc fgetc fputs fgets fprintf fscanf fwrite fread sprintf和sscanf snprintf ​编辑 4对比一组函数(prin…

【Spring篇】初识之Spring的入门程序及控制反转与依赖注入

&#x1f9f8;安清h&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;【计算机网络】&#xff0c;【Mybatis篇】 &#x1f6a6;作者简介&#xff1a;一个有趣爱睡觉的intp&#xff0c;期待和更多人分享自己所学知识的真诚大学生。 文章目录 &#x1f3af;初始Spring …

cmake 编译 01

CMakeLists.txt cmake_minimum_required(VERSION 3.10)project(MyProject)set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True)# 如果顶层 CMakeLists.txt 文件中使用了 add_subdirectory() 命令&#xff0c;CMake 会进入指定的子目录&#xff0c;并处理该目录…

每周心赏|一觉醒来,我得诺奖了?

这次诺奖名单我看了&#xff0c;我不在里面&#xff0c;说实话我很失望&#x1f62e;‍&#x1f4a8;&#xff0c;希望AI可以懂事点&#xff0c;立刻、马上为我颁奖&#xff01; AI&#xff1a;收到&#x1fae1;&#xff0c;现在就去做。 自从发现了这个可以做诺奖海报的智能…

【Python】NumPy(一):数据类型、创建数组及基本操作

目录 ​NumPy初识 1.什么是NumPy&#xff1f; NumPy的应用 NumPy数据类型 Python基本数据类型 NumPy数据类型 NumPy数组 创建数组 1.使用numpy.array() 2.使用arange()方法创建 3.使用linspace()创建等差数列 4使用zeros()创建数组 5.使用ones()创建数组 6.利用…

精华帖分享 | 从华泰研报出发,开启人工智能炼丹篇章!

本文来源于量化小论坛策略分享会板块精华帖&#xff0c;作者为1go的程序猿&#xff0c;发布于2024年3月30日。 以下为精华帖正文&#xff1a; 最近研究完邢大新发布的各种框架后&#xff0c;突然冒出了想当牛马的想法。但是&#xff0c;本人作为一个量化小白&#xff0c;从头开…

【图解版】力扣第1题:两数之和

Golang代码实现 func twoSum(nums []int, target int) []int {m : make(map[int]int)for i : range nums {if _, ok : m[target - nums[i]]; ok {return []int{i, m[target - nums[i]]}} m[nums[i]] i}return nil }

【深度学习】阿里云GPU服务器免费试用3月

【深度学习】阿里云GPU服务器免费试用3月 1.活动页面2.选择交互式建模PAI-DSW3.开通 PAI 并创建默认工作空间4.前往默认工作空间5.创建交互式建模&#xff08;DSW&#xff09;实例 1.活动页面 阿里云免费使用活动页面 2.选择交互式建模PAI-DSW 支持抵扣PAI-DSW入门机型计算用量…

ONLYOFFICE文档8.2:开启无缝PDF协作

ONLYOFFICE 开源办公套件的最新版本新增约30个新功能&#xff0c;并修复了超过500处故障。 什么是 ONLYOFFICE 文档 ONLYOFFICE 文档是一套功能强大的文档编辑器&#xff0c;支持编辑处理文档、表格、幻灯片、可填写的表单和PDF。可多人在线协作&#xff0c;支持插件和 AI 集…

C++从入门到起飞之——红黑树 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1. 红⿊树的概念 2. 红⿊树的实现 2.1 构建整体框架 2.2 红黑树的插入 2.3 红黑树的验证 2.4 红黑树…

C#学习笔记(三)

C#学习笔记&#xff08;三&#xff09; 第 二 章 命名空间和类、数据类型、变量和代码规范二、类的组成和使用分析1. 基本概念2. 类的内容组成3. 方法的初步理解 第 二 章 命名空间和类、数据类型、变量和代码规范 二、类的组成和使用分析 1. 基本概念 类是程序的基本单元&a…

能源设施安全智能守护:AI监控卫士在油气与电力领域的应用

能源行业的安全与稳定运行对于社会的可持续发展至关重要&#xff0c;无论是石油、天然气还是电力设施&#xff0c;都面临着复杂的监测需求。思通数科推出的AI视频监控卫士&#xff0c;通过应用先进的人工智能技术&#xff0c;为能源行业的安全监测提供了高效、智能的解决方案。…

Web前端高级工程师培训:使用 Node.js 构建一个 Web 服务端程序(1)

1-使用 Node.js 构建一个 Web 服务端程序 文章目录 1-使用 Node.js 构建一个 Web 服务端程序1、Node.js的安装与基础语法2、Node.js 中的 JavaScript 与 浏览器中的 JavaScript2-1、Node.js 中的 JavaScript2-2、浏览器 中的 JavaScript 3、什么是 WebServer(APP)&#xff1f;4…