JavaSE之多态

embedded/2024/10/23 2:40:42/

文章目录

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

        在这里插入图片描述

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

  个人主页

  JavaSE专栏

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

多态的概念

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

java">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. 直接赋值,代码示例如下:
java">Animal animal = New Dog("旺财", 2);
Animal animal = New Cat("小花", 1);
  1. 作为方法的参数进行传递,代码示例如下:
java">    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. 作为返回值进行接收
java">public Animal func() {Dog dog = new Dog("旺财",2);return dog;
}

向上转型的优点:

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

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

  1. 可扩展能力更强

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

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

动态绑定

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

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

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


代码示例如下:

java">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,体现动态绑定}

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


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

静态绑定

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


向下转型

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

java">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();}
}

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

java">   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关键字,代码示例如下:

java">      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方法,而这个方法的底层就是使用 '='来比较两个对象的引用,即比较两个地址是否相同。代码示例如下:

java">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方法,代码示例如下:

java">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方法如下:

java">@Overridepublic 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方法,打印出这个引用的地址,代码示例如下:

java">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方法,代码示例如下:

java">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方法如下:

java">    @Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}

同样@Override也是校验作用

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


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

java">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方法,来满足只要对象中的名字和年龄相同就是相同的地址,代码示例如下:

java">class Person {public String name;public int age;@Overridepublic 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.ppmy.cn/embedded/129711.html

相关文章

路由器概述

一、路由器的工作原理 根据路由表转发数据 二、路由表与其形成 2.1路由表 (1)概念 路由:从源主机到目的主机的转发过程路由表:路由器中维护的路由条目的集合;路由器根据路由表做路径选择 (2&#xff…

支持向量机SVM原理详解

SVM原理详解 1、超平面2、SVM原理1. 问题定义2. 分类决策得到约束条件 3. 最大化间隔4. 优化目标 3、凸优化问题1. 原始优化问题优化目标约束条件 2. 拉格朗日乘子法3. 拉格朗日函数分析4. 求解对 w w w 和 b b b 的极值5. 构造对偶问题对偶问题的约束条件: 6、通…

【含文档】基于ssm+jsp的旧物交易平台(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: apache tomcat 主要技术: Java,Spring,SpringMvc,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定义了两个…

【原创】java+springboot+mysql校园留言墙管理系统设计与实现

个人主页:程序猿小小杨 个人简介:从事开发多年,Java、Php、Python、前端开发均有涉猎 博客内容:Java项目实战、项目演示、技术分享 文末有作者名片,希望和大家一起共同进步,你只管努力,剩下的交…

【JavaEE初阶】网络编程TCP协议实现回显服务器以及如何处理多个客户端的响应

前言 🌟🌟本期讲解关于TCP/UDP协议的原理理解~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话不多说…

C#生成SVG文件(文本、线段、圆、椭圆、多边形的示例)

1. 介绍 一些参考博客: C#生成SVG涉及文字、线段、椭圆的示例 用C#解析渲染显示SVG矢量图转化格式保存 C#操作SVG矢量图-nuget库svg 开源库:https://github.com/svg-net/SVG 在NuGet上搜索SVG并安装: 2. 示例 引入的命名空间: …

Day15numpy

迭代数组 nditer是NumPy中的一个迭代器对象,用于高效地遍历多维数组。它提供了多种选项和控制参数,如遍历顺序(order)、迭代标志(flags)和操作标志(op_flags)。 order 参数 order…

算法-利用深度优先搜索求解二叉树路径问题

这道题可以用深度优先搜索来写,比如说加入节点左右节点都为空且值等于targetsum则返回true,这里可以使用一个简单的方法来写,就比如说我们每次遍历到一个节点时,用targetsum减去当前节点的值 ,这样的话只要遍历到叶子节点值等于ta…