JavaSE继承和多态(下)

在了解多态之前我们先弄清以下三个概念:

  1. 方法的重写
  2. 向上转型和向下转型
  3. 动态绑定和静态绑定

一.方法的重写

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

class Animal {
    public void eat() {
        System.out.println("正在吃");
    }
}

class Dog extends Animal {

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

class Test {
    public static void main(String[] args) {
        Dog dog=new Dog();
        dog.eat();
    }
}

 

方法重写的规则: 

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

 

 重写与重载的区别:

重写的设计原则:

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

例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅 可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的 类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我 们当今的需求了。

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

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

二.向上转移和向下转型

1.向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。  

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

Animal animal = new Cat("元宝",2);

animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。

使用场景:

  1. 直接赋值
  2. 方法传参
  3. 方法返回

1.直接赋值

class Animal {
    String name;
    public void eat() {
        System.out.println("正在吃");
    }

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

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

class Test {
    public static void main(String[] args) {
        Animal animal=new Dog("小黑");
        animal.eat();
    }
}

两种不同写法: 

2.方法传参

class Animal {
    String name;
    public void eat() {
        System.out.println("正在吃");
    }

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

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

class Test {
    public static void eatFood(Animal animal){
        animal.eat();
    }

    public static void main(String[] args) {
        eatFood(new Dog("大黄"));
    }
}

3.方法返回

class Animal {
    String name;
    public void eat() {
        System.out.println("正在吃");
    }

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

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

class Test {

    public static Animal buyAnimal(String var){
        if("狗".equals(var) ){
            return new Dog("狗狗");
        }else{
            return null;
        }
    }

    public static void main(String[] args) {

        Animal animal = buyAnimal("狗");
        animal.eat();
    }
}

总结:   以 2 为例

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。

2.向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法

但有时候可能需要调用 子类特有的方法 。

此时:将父类引用再还原为子类对象即可即向下转换

class Animal {
    String name;
    int age;

    public  Animal (String name,int age) {
        this.name=name;
        this.age=age;
    }
    public void eat() {
        System.out.println(name+"吃饭");
    }

}


class Cat extends Animal {

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(super.name+"吃鱼~~~~~");
    }
}


class Dog extends Animal {

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(super.name+"吃骨头~~~~");
    }
}


class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("猫",2);
        Dog dog = new Dog("狗", 1);
        // 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();

        // 编译失败,编译时编译器将animal当成Animal对象处理
        // 而Animal类中没有bark方法,因此编译失败
        //animal.bark();

        // 向上转型
        // 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
        // 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
        cat = (Cat)animal;
        cat.eat();

        // animal本来指向的就是狗,因此将animal还原为狗也是安全的
        dog = (Dog)animal;
        dog.eat();
    }
}

正确做法 :

所以 向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。

Java中为了提高向下转型的安全性

引入了instanceof ,如果该表达式为true,则可以安全转换。

class Animal {
    String name;
    int age;

    public  Animal (String name,int age) {
        this.name=name;
        this.age=age;
    }
    public void eat() {
        System.out.println(name+"吃饭");
    }

}


class Cat extends Animal {

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(super.name+"吃鱼~~~~~");
    }

    public void mew() {
        System.out.println(super.name+"正在mew~~~~");
    }
}


class Dog extends Animal {

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(super.name+"吃骨头~~~~");
    }

    public void bark() {
        System.out.println(super.name+"正在汪汪叫~~~~");
    }
}


class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("猫",2);
        Dog dog = new Dog("狗", 1);
// 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
        if(animal instanceof Cat){
            cat = (Cat)animal;
            cat.mew();
        }
        if(animal instanceof Dog){
            dog = (Dog)animal;
            dog.bark();
        }
    }
}

 

三.动态绑定与静态绑定

1.动态绑定

动态绑定:也称为 后期绑定(晚绑定)

即在编译时,不能确定 方法的行为 ,需要等到 程序运行 时,才能够确定具体调用那个 类的方法 。

2.静态绑定

静态绑定:也称为 前期绑定(早绑定)

class Test {
    public static int sum(int x,int y) {
        return x+y;
    }

    public static double sum (double x,double y,double z) {
        return x+y+z;
    }
    public static void main(String[] args) {

        System.out.println(sum(2, 6));

        System.out.println(sum(1.2, 3.5, 9.3));
    }
}

 即在编译时,根据用户所传递实参类型就确定了具体 调用 那个方法。

典型代表函数重载

四.多态

1.多态的概念

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

 总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。

2.多态的实现条件 

 在java中要实现多态,必须要满足如下几个条件,缺一不可:

  1. 必须在继承体系下
  2. 子类必须要对父类中方法进行重写
  3. 通过父类的引用调用重写的方法

多态体现:在代码运行时 ,当传递不同类对象时 ,会调用对应类中的方法 。

class Animal {
    String name;
    int age;

    public  Animal (String name,int age) {
        this.name=name;
        this.age=age;
    }
        public void eat() {
            System.out.println(name+"吃饭");
        }

}


class Cat extends Animal {

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(super.name+"吃鱼~~~~~");
    }

    public void mew() {
        System.out.println(super.name+"正在mew~~~~");
    }
}


class Dog extends Animal {

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(super.name+"吃骨头~~~~");
    }

    public void bark() {
        System.out.println(super.name+"正在bark~~~~");
    }
}


///分割线//

 class TestAnimal {


        // 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
        // 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
        // 注意:此处的形参类型必须时父类类型才可以
public static void eat(Animal a){

        a.eat();
    }
public static void main(String[] args) {
    Cat cat = new Cat("元宝", 2);
    Dog dog = new Dog("小七", 1);
    eat(cat);
    eat(dog);
}

}

在上述代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的.

当类的调用者在编写 eat 这个方法的时候, 参数类型为 Animal (父类)

 

当类的调用者在编写 eat 这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的 a 引用指向的是哪个类型(哪个子类)的实例. 此时 a这个引用调用 eat方法可能会有多种不同的表现(和 a 引用的实例 相关), 这种行为就称为 多态.

 3.多态的优缺点

1. 圈复杂度低

普通写法

class Shape {
    //属性....
    public void draw() {
        System.out.println("画图形!");
    }
}
class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("♦");
    }
}
class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("●");
    }
}

class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("❀");
    }
}


class Test {
    public static void drawShapes() {
        Rect rect = new Rect();
        Cycle cycle = new Cycle();
        Flower flower = new Flower();
        String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
        for (String shape : shapes) {
            if (shape.equals("cycle")) {
                cycle.draw();
            } else if (shape.equals("rect")) {
                rect.draw();
            } else if (shape.equals("flower")) {
                flower.draw();
            }
        }
    }

    public static void main(String[] args) {
        drawShapes();
    }
}

 多态写法

class Shape {
    //属性....
    public void draw() {
        System.out.println("画图形!");
    }
}
class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("♦");
    }
}
class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("●");
    }
}

class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("❀");
    }
}


class Test {
    public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
        Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
                new Rect(), new Flower()};
        for (Shape shape : shapes) {
            shape.draw();
        }
    }

    public static void main(String[] args) {
        drawShapes();
    }
}

2. 可扩展能力强

如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.

class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}

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

而对于不用多态的情况, 就要把 drawShapes 中的 if - else 进行一定的修改改动成本更高.

3. 多态的缺陷 

1.属性没有多态性

当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性

2.构造方法没有多态性

 4. 避免在构造方法中调用重写方法

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

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

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

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

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

相关文章

如何使用 langchain 与 openAI 连接

上一篇写了如何安装 langchain https://www.cnblogs.com/hailexuexi/p/18087602 这里主要说一个 langchain的使用 创建一个目录 langchain ,在这个目录下创建两个文件 main.py 这段python代码,用到了openAI,需要openAI及FQ。这里只做…

c++的学习之路:16、string(3)

上章有一些东西当时没学到,这里学到了将在补充,文章末附上代码,思维导图。 目录 一、赋值重载 二、带模板的创建 三、析构函数 四、代码 五、思维导图 一、赋值重载 这里的赋值重载就是直接利用交换函数进行把传参生成的临时数据和需要…

IDEA中的Debug功能介绍

说明:本文介绍IDEA中的Debug功能,基于2023.2(Ultimate Edition)版本 简单介绍 首先,在程序需要停止的所在行号上,鼠标左键,可设置一个断点,是一个红色圆点标志,表示程序…

2023年下半年中级软件设计师上午真题及答案解析

01 02 03 04 05 06 07 08 09 10 篇幅有限,私我获取免费完整 pdf文件

php反序列化题目

[NewStarCTF 公开赛赛道]UnserializeOne 分析代码,最终需要调用到 file_get_contents 即可获得flag 从后往前分析 触发 __invoke 需要 以调用函数的方式调用一个对象 可以找到Start类 里的__isset中可以将类当作函数调用 所以需要调用到 __isset 就需要 isset()…

Steam上线真人乙游,女性玩家还愿意买单吗?

Steam上线了一款真人乙游《糟糕!他们太爱我了怎么办?》(以下简称《糟糕!)。 乍一听这个游戏名,似乎和《完蛋!我被美女包围了!》有异曲同工之妙,事实也确实如此&#xff…

实现通讯录(顺序表版本)

一、功能要求 (1)⾄少能够存储100个⼈的通讯信息 (2)能够保存⽤⼾信息:名字、性别、年龄、电话、地址等 (3)增加联系⼈信息 (4)删除指定联系⼈ (5&#…

国内:深圳交通流量数据集

数据来源:深圳政府数据开放平台(深圳市政府数据开放平台),这个官网上还有其他类数据集,值得收藏!!! 数据集介绍:宝安区-G4高速西乡大道入口车流量统计 第一行每列的标题…

什么是超导悬浮?工作原理是什么?

某些材料在冷却到某个温度(也称为“临界温度”)以下时会完全失去电阻。 1910 年,一位名叫 Heike Kamerlingh Onnes 的荷兰物理学家发现了这一现象。他注意到低于一定温度时电阻突然下降,然后他大胆地声称发现了一种新的物质状态&a…

字符串处理

读取 先定义: char ch[100];string s; cin>>s或cin>>ch以空格或换行符结束gets(ch);//gets只能读字符数组,不能直接读字符串stringgets和getline会把第一次出现的换行符及先前的字符串读进去(包括空格)&#xff0…

利用Flutter框架实现iOS应用的跨平台发布策略

本文探讨了使用Flutter开发的iOS应用能否上架,以及上架的具体流程。苹果提供了App Store作为正式上架渠道,同时也有TestFlight供开发者进行内测。合规并通过审核后,Flutter应用可以顺利上架。但上架过程可能存在一些挑战,因此可能…

【Linux】正则表达式实验操作实例

正则表达式是一种强大的工具,用于在文本中查找、匹配和替换特定的字符串模式。 实验目的 掌握正则表达式的表达方式掌握grep/egrep命令的用法掌握sed 命令的用法掌握awk命令的用法 正则表达式 实验目的实验内容实验过程创建grep文件来进行如下操作用sed命令完成下列…

HAL STM32 定时器PWM DMA输出方式

HAL STM32 定时器PWM DMA输出方式 🧨遗留问题:当配置RCR重复计数器,配置为2时,在定义了3组PWM参数情况下,只能输出第二组参数的PWM波形。(HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, aCCValue_Buff…

Java中网络编程,Junit单元测试详解

文章目录 软件结构C/S结构B/S结构 概述三要素IP (银行的位置)端口 (银行中某个柜台号)协议 (填写取款单的规则)TCP通信程序TCP通信原理客户端发送数据服务端接收数据过程图三次握手 Junit单元测试概述常见的注解使用断言概述使用 软件结构 C/S结构 客户…

怀俄明探空站数据解算PWV和Tm

1. Matlab 获取代码可关注公众号WZZHHH回复(怀俄明探空站数据解算PWV和Tm),或者咸鱼关注:WZZHHH123 怀俄明探空站数据解算PWV和Tm: 有关 Matlab 获取代码可关注公众号WZZHHH回复(怀俄明多线程下载&#…

mysql_secure_installation初始化数据库报Access denied

使用mysql_secure_installation配置时出错: 可能输入密码错误,重新复制,粘贴密码。 或者: 登录mysql,设置密码,然后再设置权限。 mysql -u root -p [输入密码] sql>set passwordpassword("newPa…

数据同步工具datax安装配置与示例

文章目录 一、部署步骤1、jdk环境2、python环境步骤一:安装方式一:官网下载安装包方式二:brew命令安装 步骤二:配置环境变量步骤三:验证 3、maven环境(可选) 二、下载安装datax1、下载datax源码…

100 个网络基础知识,看完成半个网络高手

100 个网络基础知识,看完成半个网络高手。 1)什么是链接? 链接是指两个设备之间的连接。它包括用于一个设备能够与另一个设备通信的电缆类型和协议。 2)OSI 参考模型的层次是什么? 有 7 个 OSI 层:物理层,数据链路层,网络层&…

入门微调预训练Transformer模型

大家好,HuggingFace 为众多开源的自然语言处理(NLP)模型提供了强大的支持平台,让这些模型能够通过训练和微调来更好地服务于各种特定的应用场景。在大型语言模型(LLM)迅猛发展的今天,HuggingFac…

配置vscode用于STM32编译,Debug

配置环境参考: Docs 用cubemx配置工程文件,用VScode打开工程文件。 编译的时候会有如下报错: vscode出现process_begin :CreateProcess failed 系统找不到指定文件 解决方案:在你的makefile中加上SHELLcmd.exe就可以了 参考…