Java多态和继承(下篇)

今天接着学习多态和继承

目录

  • 1 继承
    • 1.1 再谈初始化
    • 1.2 protect关键字
    • 1.3 继承方式
    • 1.4 final 关键字
    • 1.5 组合
  • 2 多态
    • 2.1 多态的概念
    • 2.2 多态实现条件
    • 2.3 重写
    • 2.4 向上转型和向下转型
      • 2.4.1 向上转型
      • 2.4.2 向下转型
    • 2.5 多态的优缺点
    • 2.6 避免在构造方法中使用重写的方法
  • 总结

1 继承

1.1 再谈初始化

我们简单回顾一下几个重要的代码块:
实例代码块和静态代码块。在没有继承关系时的执行顺序。

class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;System.out.println("构造方法执行");}{System.out.println("实例代码块执行");}static {System.out.println("静态代码块执行");}
}public static class TestDemo {public static void main(String[] args) {Person person1 = new Person("xiaoxin",10);System.out.println("============================");Person person2 = new Person("hyzs",20);}
}

在这里插入图片描述

  1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
  2. 当有对象创建时,才会执行实例代码块,
    实例代码块执行完成后,最后构造方法执行
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;System.out.println("Person:构造方法执行");}{System.out.println("Person:实例代码块执行");}static {System.out.println("Person:静态代码块执行");}
}class Student extends Person{public Student(String name,int age) {super(name,age);System.out.println("Student:构造方法执行");}{System.out.println("Student:实例代码块执行");}static {System.out.println("Student:静态代码块执行");}
}public class TestDemo4 {public static void main(String[] args) {Student student1 = new Student("xiaoxin1",19);System.out.println("===========================");Student student2 = new Student("hyzs",20);}public static void main1(String[] args) {Person person1 = new Person("xiaoxin2",10);System.out.println("============================");Person person2 = new Person("hyzs",20);}
}

Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
===========================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行

通过分析执行结果,得出以下结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,
父类和子类的静态代码块都将不会再执行

1.2 protect关键字

Java中引入了访问限定符,
主要限定:类或者类中成员能否在类外或者其他包中被访问
在这里插入图片描述
那父类中不同访问权限的成员,
在子类中的可见性又是什么样子的呢

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {private int a;protected int b;public int c;int d;
}// extend01包中
// 同一个包中的子类
public class D extends B{public void method(){// super.a = 10;     // 编译报错,父类private成员在相同包子类中不可见super.b = 20;         // 父类中protected成员在相同包子类中可以直接访问super.c = 30;         // 父类中public成员在相同包子类中可以直接访问super.d = 40;         // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问}
}// extend02包中
// 不同包中的子类
public class C extends B {public void method(){// super.a = 10;     // 编译报错,父类中private成员在不同包子类中不可见super.b = 20;        // 父类中protected修饰的成员在不同包子类中可以直接访问super.c = 30;        // 父类中public修饰的成员在不同包子类中可以直接访问//super.d = 40;     // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问}
}// extend02包中
// 不同包中的类
public class TestC {public static void main(String[] args) {C c = new C();c.method();// System.out.println(c.a);   // 编译报错,父类中private成员在不同包其他类中不可见// System.out.println(c.b);   // 父类中protected成员在不同包其他类中不能直接访问System.out.println(c.c);      // 父类中public成员在不同包其他类中可以直接访问// System.out.println(c.d);   // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问}
}

注意:父类中private成员变量虽然在子类中不能直接访问,
但是也继承到子类中了

1.3 继承方式

在现实生活中,事物之间的关系是非常复杂,灵活多样
在这里插入图片描述
但在Java中只支持以下几种继承方式
在这里插入图片描述
注意:Java中不支持多继承

1.4 final 关键字

final关键可以用来修饰变量、成员方法以及类

  1. 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20;  // 编译出错
  1. 修饰类:表示此类不能被继承
final public class Animal {...
}
public class Bird extends Animal {...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
  1. 修饰方法:表示该方法不能被重写(后续介绍)

1.5 组合

和继承类似, 组合也是一种表达类之间关系的方式,
也是能够达到代码重用的效果。
组合并没有涉及到特殊的语法(诸如 extends 这样的关键字),
仅仅是将一个类的实例作为另外一个类的字段

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车

在这里插入图片描述
汽车和其轮胎、发动机、方向盘、车载系统等的关系
就应该是组合,因为汽车是由这些部件组成的

// 轮胎类
class Tire{// ...
}
// 发动机类
class Engine{// ...
}
// 车载系统类
class VehicleSystem{// ...
}
class Car{private Tire tire;          // 可以复用轮胎中的属性和方法private Engine engine;      // 可以复用发动机中的属性和方法private VehicleSystem vs;   // 可以复用车载系统中的属性和方法// ...
}
// 奔驰是汽车
class Benz extend Car{// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}

组合和继承都可以实现代码复用,应该使用继承还是组合,
需要根据应用场景来选择,一般建议:能用组合尽量用组合

2 多态

2.1 多态的概念

多态的概念:通俗来说,就是多种形态,
具体点就是去完成某个行为,
当不同的对象去完成时会产生出不同 的状态
总的来说:同一件事情,
发生在不同对象身上,就会产生不同的结果

2.2 多态实现条件

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

  1. 必须在继承体系下
  2. 子类必须要对父类中方法进行重写
  3. 通过父类的引用调用重写的方法
    多态体现:在代码运行时,当传递不同类对象时,
    会调用对应类中的方法。
public 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 + "吃饭");}}public class Cat extends Animal{public Cat(String name, int age){super(name, age);}@Overridepublic void eat(){System.out.println(name+"吃鱼~~~");}}public class Dog extends Animal {public Dog(String name, int age){super(name, age);}@Overridepublic void eat(){System.out.println(name+"吃骨头~~~");}
}///分割线//
public 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);}
}运行结果:
元宝吃鱼~~~
元宝正在睡觉
小七吃骨头~~~
小七正在睡觉

在这里插入图片描述

2.3 重写

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

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

在这里插入图片描述
方法重载是一个类的多态性表现,
而方法重写是子类与父类的一种多态性表现
在这里插入图片描述
对于已经投入使用的类,尽量不要进行修改。
最好的方式是:重新定义一个新的类,
来重复利用其中共性的内容,并且添加或者改动新的内容

静态绑定:也称为前期绑定(早绑定),即在编译时,
根据用户所传递实参类型就确定了具体调用那个方法。
典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,
不能确定方法的行为,需要等到程序运行时,
才能够确定具体调用那个类的方法

2.4 向上转型和向下转型

2.4.1 向上转型

向上转型:实际就是创建一个子类对象,
将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()

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

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

在这里插入图片描述

  1. 直接赋值
  2. 方法传参
  3. 方法返回
public class TestAnimal {// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象public static void eatFood(Animal a){a.eat();}// 3. 作返回值:返回任意子类对象public static Animal buyAnimal(String var){if("狗".equals(var) ){return new Dog("狗狗",1);}else if("猫" .equals(var)){return new Cat("猫猫", 1);}else{return null;}}public static void main(String[] args) {Animal cat = new Cat("元宝",2);   // 1. 直接赋值:子类对象赋值给父类对象Dog dog = new Dog("小七", 1);eatFood(cat);eatFood(dog);Animal animal = buyAnimal("狗");animal.eat();animal = buyAnimal("猫");animal.eat();}}

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

2.4.2 向下转型

将一个子类对象经过向上转型之后当成父类方法使用,
再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换
在这里插入图片描述

public 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实际指向的是狗// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastExceptioncat = (Cat)animal;cat.mew();// animal本来指向的就是狗,因此将animal还原为狗也是安全的  dog = (Dog)animal;dog.bark();}
}

向下转型用的比较少,而且不安全,
万一转换失败,运行时就会抛异常。
Java中为了提高向下转型的安全性,引入了 instanceof ,
如果该表达式为true,则可以安全转换

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

instanceof 关键词官方介绍:instanceof 关键词官方介绍

2.5 多态的优缺点

class Shape {//属性....public void draw() {System.out.println("画图形!");}
}class Rect extends Shape{@Overridepublic void draw() {System.out.println("♦");}
}class Cycle extends Shape{@Overridepublic void draw() {System.out.println("●");}
}class Flower extends Shape{@Overridepublic void draw() {System.out.println("❀");}
}
  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else

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

例如我们现在需要打印的不是一个形状了, 而是多个形状.
如果不基于多态, 实现代码如下:

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

如果使用使用多态, 则不必写这么多的 if - else 分支语句,
代码更简单

public static void drawShapes() {// 我们创建了一个 Shape 对象的数组.Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()};for (Shape shape : shapes) {shape.draw();}
}
  1. 可扩展能力更强
    如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.
class Triangle extends Shape {@Overridepublic void draw() {System.out.println("△");}
}

对于类的调用者来说(drawShapes方法),
只要创建一个新类的实例就可以了, 改动成本很低.
而对于不用多态的情况,
就要把 drawShapes 中的 if - else 进行一定的修改, 改动成本更高.

多态缺陷:代码的运行效率降低。

  1. 属性没有多态性
    当父类和子类都有同名属性的时候,通过父类引用,
    只能引用父类自己的成员属性
  2. 构造方法没有多态性

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

一段有坑的代码. 我们创建两个类,
B 是父类, D 是子类. D 中重写 func 方法.
并且在 B 的构造方法中调用 func

class B {public B() {// do nothingfunc();}public void func() {System.out.println("B.func()");}
}
class D extends B {private int num = 1;@Overridepublic void func() {System.out.println("D.func() " + num);}
}
public class Test {public static void main(String[] args) {D d = new D();}
}
// 执行结果
D.func() 0

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

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

总结

今天对继承和多态的学习就先到这里了,如果感觉不错,希望可以给博主点赞收藏和关注,感谢大家的支持,还有什么问题和建议可以在评论区评论,拜拜。

总结

在这里插入图片描述

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

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

相关文章

动态规划理论基础和习题【力扣】【算法学习day.25】

前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向(例如想要掌握基础用法,该刷哪些题?)我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非常非常高滴&am…

数据结构之顺序表(C语言)

1 线性表 线性表是n个具有相同特性的数据元素的有限序列,是一种在实际中广泛应用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串等。 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是…

Qt——窗口

一.窗口概述 Qt 窗口是通过 QMainWindow 类来实现的。 QMainWindow是一个为用户提供主窗口程序的类,继承QWidget类,并且提供一个预定义的布局。包含一个菜单栏(menu bar),多个工具栏(tool bars&#xff0…

长亭那个检测能力超强的 WAF,出免费版啦

告诉你们一个震撼人心的消息,那个检测能力超强的 WAF——长亭雷池,他推出免费社区版啦,体验地址见文末。 八年前我刚从学校毕业,在腾讯做安全研究,看到宇森在 BlackHat 上演讲的议题 《永别了,SQL 注入》 …

漏洞分析 | Spring Framework路径遍历漏洞(CVE-2024-38816)

漏洞概述 VMware Spring Framework是美国威睿(VMware)公司的一套开源的Java、JavaEE应用程序框架。该框架可帮助开发人员构建高质量的应用。 近期,网宿安全演武实验室监测到Spring Framework在特定条件下,存在目录遍历漏洞&…

tp接口 入口文件 500 错误原因

一、描述 二、可能的原因 1、runtime目录没权限 2、关闭了Tp记录日志的功能 3、关闭debug调试模式 4、关闭了debug模式还是报错 一、描述 Thinkphp项目本地正常,上传到线上后静态文件访问正常,访问tp接口报500错误。 经调试发现,在php入…

第07章 运算符的使用

一、算数运算符 算术运算符主要用于数学运算,其可以连接运算符前后的两个数值或表达式,对数值或表达式进行加 ()、减(-)、乘(*)、除(/)和取模(%&a…

十七 MyBatis的注解式开发

十七、MyBatis的注解式开发 mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。 当然,使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。 官方是这么说的: 使…

用 Python 写了一个天天酷跑(附源码)

Hello,大家好,给大家说一下,我要开始装逼了 这期写个天天酷跑玩一下叭! 制作一个完整的“天天酷跑”游戏涉及很多方面,包括图形渲染、物理引擎、用户输入处理、游戏逻辑等。由于Python是一种高级编程语言,…

Kettle——CSV文件转换成excel文件输出

1.点击—文件—新建—转换 拖入两个组件: 按shift+鼠标左击建立连接,并点击主输出步骤, 点击CSV文件输入,选择浏览的csv文件,然后点击确定 同样,Excel也同上,只是要删除这个xls 并…

高效管理iPhone存储:苹果手机怎么删除相似照片

在使用iPhone的过程中,我们经常会遇到存储空间不足的问题,尤其是当相册中充满了大量相似照片时。这些照片不仅占用了宝贵的存储空间,还可能使iPhone出现运行卡顿的情况。因此,我们迫切需要寻找苹果手机怎么删除相似照片的方法&…

用示例来看C2Rust工具的使用和功能介绍

C2Rust可以将C语言的源代码转换成Rust语言的源代码。下面是一个简单的C语言代码示例&#xff0c;以及使用c2Rust工具将其转换为Rust安全代码的过程。 C语言源代码示例 // example.c #include <stdio.h>int add(int a, int b) {return a b; }int main() {int result a…

赛普EAP平台 Download.aspx 任意文件读取漏洞复现

0x01 产品描述&#xff1a; ‌赛普EAP平台‌是一款专门为房地产企业打造的数字化管理系统&#xff0c;旨在帮助企业实现业务流程的优化、管理效率的提升和客户体验的改善。该系统集成了项目管理、销售管理、客户关系管理、财务管理、报表分析等多个模块&#xff0c;能够满足企业…

前端三件套-css

一、元素选择器 元素选择器&#xff1a;利用标签名称。p,h1-h6...... 行内样式&#xff08;内联样式&#xff09;&#xff1a;例如<p style"color:red;font-size:50px"> id选择器&#xff1a;针对某一个特定的标签来使用。以#定义。 class&#xff08;类&a…

服务器被攻击排查记录

起因 我的深度学习的所有进程突然被killed&#xff0c;我以为是检修&#xff0c;后面发现好像简单的python代码可以正常运行。但是我的训练进程一启动就会被killed 第一时间没有用htop查看cpu&#xff0c;用top看着挺正常的&#xff0c;但是后面看htop&#xff0c;全是绿的&a…

项目实战:基于Linux的Flappy bird游戏开发

一、项目介绍 项目总结 1.按下空格键小鸟上升&#xff0c;不按小鸟下落 2.搭建小鸟需要穿过的管道 3.管道自动左移和创建 4.小鸟撞到管道游戏结束 知识储备 1.C语言 2.数据结构-链表 3.Ncurses库 4.信号机制 二、Ncurses库介绍 Ncurses是最早的System V Release 4.0 (SVr4)中…

抖音小程序看广告变现秘籍:构建用户粘性与点击收益长期价值解析

在抖音小程序看广告变现的宏伟蓝图中&#xff0c;构建用户粘性和挖掘用户长期价值是核心环节&#xff0c;这是实现丰厚收益和打造高效盈利新引擎的重要保障。 要构建用户粘性&#xff0c;首先要提供优质且持续更新的内容。以一个知识科普类小程序为例&#xff0c;需要不断推出新…

L0G1000:Linux+InternStudio 闯关作业

1. 配置基础环境 首先&#xff0c;打开 Intern Studio 界面&#xff0c;点击 创建开发机 配置开发机系统。 InternStudio 填写 开发机名称 后&#xff0c;点击 选择镜像 使用 Cuda11.7-conda 镜像&#xff0c;然后在资源配置中&#xff0c;使用 10% A100 * 1 的选项&#xff…

【ArcGISPro】单次将自己建立的工具箱添加至Arcpy中

新建工具箱 添加至Arcpy中 调用刚添加的工具箱

【vue2.7.16系列】手把手教你搭建后台系统__登录使用状态管理(15)

使用store进行登录信息管理 其实就是把登录放到vuex的actions中去执行&#xff0c;然后保存用户信息、权限等 在store/modules/account.js中添加如下代码&#xff1a; import { login, logout, getInfo, menusApi } from /api/account; // getExpiresTime import {getToken,s…