【JavaSE】面向对象---多态

前言

  1. 本篇以Java初学者视角写下,难免有不足,或者术语不严谨之处。如有错误,欢迎评论区指正。
  2. 本篇说明多态相关的知识。
  3. 若本文无法解决您的问题,可以去最下方的参考文献出,找出想要的答案。

在这里插入图片描述

多态概念

多态(polymorphism):多种形态,具体是不同对象完成某个行为,它们的状态不同。
比如,同样是跑1000米,有些人气喘吁吁(我),还有一些大佬跑完气都不带喘的。
我认为多态是一种思想。而要理解多态得熟悉向上转型,和方法重写的知识。
有关笔者继承的博客:可以浅读了解一下

向上转型

字面意思:转型,类型转换;向上,即子类向超类转换。
向上转型属于自动转换类型可以不用强制类型转换运算符。

public class Main {

    public static void main(String[] args) {
	Dog dog =Dog.Init("旺财",2);//这里采用的是静态工厂方法,不熟悉的可以看下面的参考文献阅读一下。
    Animal animal = dog;//这里Dog类转换成了父类Animal
    }
}

不熟悉的可以先跳过,这里只是为了代码的完整性放这儿/

//Dog.java
class Dog extends Animal{

    public static Dog Init(String name,int age){
        Dog dog = new Dog(name,age);
        return dog;
    }
    public Dog(String name,int age){
        super(name,age);
    }
    public Dog(){

    }
    @Override
    public void eat() {
        System.out.println(this.name+" 正在吃狗粮.......");
    }
}

//Animal.java
class Animal {
    String name;
    int age;

    public Animal(String name,int age){
        this.name=name;
        this.age =age;
    }
    public Animal(){

    }
    public static void fun(Animal animal){
        animal.eat();
    }
    public void eat(){
        System.out.println(this.name + "正在吃法。");
    }

}

在Java中,对象变量是多态的,它既可以引用自身的类型,还可以引用其子类的对象。

实现向上转型有三种方式
1.直接赋值

 Animal animal = new Dog();//直接创建一个子类对象然后赋值给超类的对象变量

2.方法传参

//Main.java
public class Main {

    public static void main(String[] args) {

        Animal.fun1(new Dog());
    }
}

//Animal.java类中有如下静态方法
public static void fun1(Animal animal){
 }

这种通过调用超类的方法传子类的实参来达到向上转型的效果。

3.返回值

//Animal.java中有如下静态方法。
//函数内部实例化子类对象,然后由于返回值要与类型匹配,自动地发生向上转型。
public static Animal fun2(){
        Dog dog = new Dog();
        return dog;
    }

动态绑定

这里说明动态绑定,但涉及到方法重写(先忽略这个问题)。
以下是三个类,两个子类一个超类,重点关注每个类中的eat方法.

//Dog.java
class Dog extends Animal{

    public static Dog Init(String name,int age){
        Dog dog = new Dog(name,age);
        return dog;
    }
    public Dog(String name,int age){
        super(name,age);
    }

    //关注eat方法
    @Override
    public void eat() {
        System.out.println(this.name+" 正在吃狗粮.......");
    }
}

//Animal.java
class Animal {
    String name;
    int age;

    public Animal(String name,int age){
        this.name=name;
        this.age =age;
    }

    public static void fun(Animal animal){
        animal.eat();
    }
    //关注eat方法
    public void eat(){
        System.out.println(this.name + "正在吃饭。");
    }

}

//Bird.java
class Bird extends Animal{
    public static Bird Init(String name,int age){
        Bird bird = new Bird(name,age) ;
        return bird;
    }

    public Bird(String name,int age){
        super(name,age);
    }
    //关注eat方法
    @Override
    //eren jeger
    public void eat() {
        System.out.println(this.name+" 正在吃虫子.......");
    }
}

🆗接下来来分析Main类了

public class Main {

    public static void main(String[] args) {
        Animal animal= Dog.Init("kunkun",1);
        animal.eat();//这里的eat调用的是哪个类里面eat方法?
    }
}

在这里插入图片描述

调用的是子类Dog,为什么呢?这里不是向上转型成Animal(超类)了吗?
嗯,这里就得介绍本标题的主角:动态绑定机制

  1. 动态绑定机制,简单来说,以父类为编译类型,创建子类对象的时候,绑定子类运行类型,当我们再调用方法的时候,能够有序的寻找方法,实现方法的有序调用。
  2. 解释:1.动态绑定机制在运行时调用方法绑定子类运行。
    2.程序在编译时,确实调用的时父类的eat方法。当运行代码的时候,通过父类的引用,调用了父类和子类重写的方法,结果实际调用了子类的方法,此时称这种情况为动态绑定。
    3.程序运行并且采用动态绑定调用方法时,JVM会调用父类所引用对象的实际类型的方法。

    三种解释任选理解。下面来说明方法重写。

方法重写

在上面的例子中用到方法重写,我们这里详细说说。

//Dog类中(子类)
 public void eat() {
        System.out.println(this.name+" 正在吃狗粮.......");
    }
//Bird类中(子类)
 public void eat() {
        System.out.println(this.name+" 正在吃虫子.......");
    }
//Animal类中(超类)
 public void eat(){
        System.out.println(this.name + "正在吃饭。");
    }

显而易见,Animal执行eat打印的是某某正在吃饭,而Dog打印的是某某吃狗粮。
超类的有些方法对子类并不适用,这里就是不够具体。每种动物的食物不同,这里就可以通过方法重写来针对特定的类写对应的eat方法来覆盖原先超类的方法。
没错,这就是方法重写(又称覆盖方法)。

若子类和超类的方法满足以下:
1.方法名相同
2.参数相同(参数类型,参数个数,参数序列相同)
3.返回类型相同
则构成方法重写。

方法重写的注意事项:

1.若方法前面有private static final 修饰,或者其本身是构造器(构造方法),编译器本身可以确定应该调用哪些方法。这些方法不能重写。
2.方法重写时,子类的访问权限一定大于等于超类的访问权限。若超类是public,则子类也必须是public类,不能protected等等。
3.子类和超类的覆盖方法返回类型可以不同,但必须构成父子关系。
`

//Animal类一部分
 public Animal eat(){
        System.out.println(this.name + "正在吃饭。");
        return null;
    }
    //Dog类一部分
 public Dog eat() {
        System.out.println(this.name+" 正在吃狗粮.......");
        return null;
    }
}

重写与重载区别

重写与重载都是一种多态。
方法重载是类之间的多态。方法重写是子类与超类的多态。

方法重载:
方法之间同名,但参数列表必须修改。
返回类型,访问修饰符随意。

方法重写:
1.方法名必须相同
2.参数相同(参数类型,参数个数,参数序列相同)
3.返回类型相同(构成父子关系可以不同)
4.访问修饰符必须满足子类权限大于等于超类。

下面是补充部分:

补充

向下转型

向下转型有风险。向下转型是超类强制转换成子类。这里必须借助强制转换运算符。
系统来说:将一个超类类型的引用变量强制转换成子类的过程叫做向下转型。
但不是所有对象都能向下转型,只要当这个对象原本就是子类向上转型过来时才能向下转型。
???啥意思呢?
举例

public class Main {

    public static void main(String[] args) {
        Animal animal= new Animal("kunkun",1);//直接new一个超类对象向下转型
        Dog dog =(Dog)animal;
        dog.eat();//结果如何呢?打印Dog的eat还是Animal的eat呢?

    }
}

结果是都不打印,直接报错。
在这里插入图片描述

//本来是狗,还原成狗,可以。
public class Main {

    public static void main(String[] args) {
        Animal animal= Dog.Init("kunkun",2);
        Dog dog =(Dog)animal;
        dog.eat();

    }
}

```java
//本来是狗,还原成鸟不合理。你家狗会飞???
public class Main {

    public static void main(String[] args) {
        Animal animal= Dog.Init("kunkun",2);
        Bird bird =(Bird)animal;
        bird.fly();

    }
}

在这里插入图片描述


> 总结:
> 向下转型唯一正确的使用用法:
> 1.先向上转型,让父类引用子类对象。
> 2.向下转型类型要与最初子类类型相同。别从一个子类转换成另一个子类了。报错警告!

##  instanceof关键字
**instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。**
[instanceof关键字详解](http://t.csdnimg.cn/IIZbD)
```java
public class Main {

    public static void main(String[] args) {
        Animal animal= Bird.Init("kunkun",2);
        Bird bird =(Bird)animal;
        if( bird instanceof Bird)
        {
            bird.fly();
        }else{
            System.out.println(false);
        }

    }
}

静态绑定

其实前面提过:若方法前面有private static final 修饰,构造器(构造方法),编译器本身可以确定在那个类调用这些方法。
像上述在这种程序执行前就已经绑定了方法在那个类中调用的方法称为静态绑定。

避免在构造方法中使用重写

//Test.java
class B {
public B() {
// do nothing
    func();
}
public void func() {
    System.out.println("B.func()");
}
}
class D extends B {
     private int num = 1;
      public void func() {
         System.out.println("D.func() " + num);
     }
}
public class Test {
   public static void main(String[] args) {
        D d = new D();
}
}

看main方法,首先实例化D类对象,先进入D中构造方法,构造方法为默认构造方法,由于继承关系,子类构造还有里面还要调用父类构造方法。父类构造方法里的调用func()即子类和父类方法重写,由于动态绑定,会调用子类的func(),而此时子类的num还没初始化为1,默认为0.所以最终打印结果为 D.func() 0
在这里插入图片描述

总之,构造器中最后不要调用实例化的方法,调用静态绑定的方法比如final,private修饰的方法。因为,若实例方法被子类重写,那么就动态绑定了,会调用子类的方法。

参考文献

  • Instance关键字详解
  • 静态绑定与动态绑定
  • 重载与重写—强烈建议看此篇
  • 向上转型与向下转型
  • 类加载顺序
    愿此行,终抵群星。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何实现网站HTTPS访问

在当今网络安全至关重要的时代,HTTPS已经成为网站安全的基本标准。HTTPS(超文本传输安全协议)通过在HTTP协议基础上加入SSL加密层,确保了数据在用户浏览器和服务器之间的传输是加密的,有效防止数据被窃取或篡改&#x…

【学术小白成长之路】01三方演化博弈(基于复制动态方程) -基础概念与模型构建

1.演化博弈基础知识 经典博弈论起源于1944年Von Neumann和Morgenstern合著的《博弈论与经济学行为》,是研究理性决策者之间竞争和合作关系的数学方法。 博弈论主要研究完全理性的博弈个体为实现利益最大化而做的策略选择,在过去几十年取得了极大发展&am…

【JMeter接口测试工具】第二节.JMeter基本功能介绍(下)【入门篇】

文章目录 前言八、Jmeter常用逻辑控制器 8.1 如果(if)控制器 8.2 循环控制器 8.3 ForEach控制器九、Jmeter关联 9.1 正则表达式提取器 9.2 xpath提取器 9.3 JSON提取器十、跨越线程组传值 10.1 高并发 10.2 高频…

详解 Flink 的时间语义和 watermark

一、Flink 时间语义类型 Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink 通过时间戳分配器访问事件时间戳Ingestion Time :是数据进入 Flink…

K8s速览

k8s的核心能力 ● 服务发现与负载均衡 ● 服务恢复 ● 服务伸缩 ● 自动发布与回滚 ● 批量执行 架构 server-client两层架构,Master作为中央管控节点,会和每一个Node进行一个连接; 所有UI层,client的操作,只会和Mat…

spark复习

第一章 1.​大数据特点:4V 2.​大数据计算模式 3.​hadoop生态系统 4.​spark提供了内存计算和基于DAG的任务调度机制,遵循一个软件栈满足不同应用场景的理念。 5.​hadoop中MapReduce计算框架的缺点,对应的spark的优点 第二章 1.​spark生态系统 …

shell编程(二)——字符串与数组

本文为shell 编程的第二篇,介绍shell中的字符串和数组相关内容。 一、字符串 shell 字符串可以用单引号 ‘’,也可以用双引号 “”,也可以不用引号。 单引号的特点 单引号里不识别变量单引号里不能出现单独的单引号(使用转义符…

[经验] 涠洲岛在广西吗 #职场发展#知识分享#媒体

涠洲岛在广西吗 广西涠洲岛,是中国南海上的一颗闪亮明珠,位于广西北部湾沿海,东经108.71度,北纬21.54度,距离北海市区30公里,是中国最大的海岛之一,风景秀丽,气候温和。岛上山青水秀…

PowerDesigner导入Excel模板生成数据表

PowerDesigner导入Excel模板生成数据表 1.准备好需要导入的Excel表结构数据,模板内容如下图所示 2.打开PowerDesigner,新建一个physical data model文件,填入文件名称,选择数据库类型 3.点击Tools|Execute Commands|Edit/Run Script菜单或按下快捷键Ctrl Shift X打开脚本窗口…

Ansys的电磁场分析和系统电路仿真软件Electronics 2024 R1版本在Windows系统的下载与安装配置

目录 前言一、许可管理工具安装二、许可配置三、EM安装四、MCAD和帮助文件安装(可选择,非必要)总结 前言 “ ANSYS Electromagnetics Suite或ANSYS Electronics Suite是几个功能强大的程序的集合,用于仿真系统的电磁。ANSYS电磁套…

vscode中jupyter notebook执行bash命令,乱码解决方法

问题描述 使用vscode中使用jupyter notebook执行bash命令时,不管是中文还是英文,输出均是乱码 但是使用vscode的terminal执行同样的命令又没有问题,系统自带的cmd也没有问题。 最终解决后的效果如下: ## 问题分析 默认vscode会选择使用cmd执行shell, 但是通过vscode的设…

docker部署使用本地文件的fastapi项目

项目背景:项目使用python开发,需要使用ubutun系统部署后端api接口,对外使用8901端口。 1:项目结构: 2:项目需要使用的pyhton版本为3.9,dockerfile内容如下: # FROM python:3.9# WORKDIR /co…

SpringBoot: 可执行jar的特殊逻辑

这一篇我们来看看Java代码怎么操作zip文件(jar文件),然后SpringBoot的特殊处理,文章分为2部分 Zip API解释,看看我们工具箱里有哪些工具能用SpringBoot的特殊处理,看看SpringBoot Jar和普通Jar的不同 1. Zip API解释 1. ZipFil…

【小白专用】C# Task 类异步操作-浅谈

注解 Task类表示不返回值并且通常以异步方式执行的单个操作。 Task 对象是在 .NET Framework 4 中首次引入的 基于任务的异步模式 的中心组件之一。 由于对象执行的工作 Task 通常在线程池线程上异步执行,而不是在主应用程序线程上同步执行,因此可以使用…

Adobe Illustrator 矢量图设计软件下载安装,Illustrator 轻松创建各种矢量图形

Adobe Illustrator,它不仅仅是一个简单的图形编辑工具,更是一个拥有丰富功能和强大性能的设计利器。 在这款软件中,用户可以通过各种精心设计的工具,轻松创建和编辑基于矢量路径的图形文件。这些矢量图形不仅具有高度的可编辑性&a…

检测五个数是否一样的算法

目录 算法算法的输出与打印效果输出输入1输入2 打印打印1打印2 算法的流程图总结 算法 int main() {int arr[5] { 0 };int i 0;int ia 0;for (i 0; i < 5; i) { scanf("%d", &arr[i]); }for (i 1; i < 5; i) {if (arr[0] ! arr[i]) {ia 1;break;} }…

SpringBoot高手之路-springboot原理篇

配置文件优先级 SpringBoot原理篇-多环境配置

Elasticsearch 认证模拟题 - 13

一、题目 集群中有索引 task3&#xff0c;用 oa、OA、Oa、oA 查询结构是 4 条&#xff0c;使用 dingding 的查询结果是 1 条。通过 reindex 索引 task3 为 task3_new&#xff0c;能够使 task3_new 满足以下查询条件。 使用 oa、OA、Oa、oA、0A、dingding 查询都能够返回 6 条…

R语言 | 使用最简单方法添加显著性ggpubr包

本期教程原文&#xff1a;使用最简单方法添加显著性ggsignif包 本期教程 获得本期教程代码和数据&#xff0c;在后台回复关键词&#xff1a;20240605 小杜的生信笔记&#xff0c;自2021年11月开始做的知识分享&#xff0c;主要内容是R语言绘图教程、转录组上游分析、转录组下游…

立创·天空星开发板-GD32F407VE-GPIO

本文以 立创天空星开发板-GD32F407VET6-青春版 作为学习的板子&#xff0c;记录学习笔记。 立创天空星开发板-GD32F407VE-GPIO 基础概念三极管MOS管 GPIO输出模式输出线与GPIO输入模式GPIO点灯 基础概念 GPIO&#xff0c;全称为“通用输入/输出”&#xff08;General Purpose …