Java三大特征之继承与多态

class 子类 extends 父类 {

}

规则

  • extends是继承的关键字

  • Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).

  • 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.

代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法),有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联,例如以下代码

在src内创建一个包testdome,在testdome创建了三个不同的文件,每个文件代表一个类

在这里插入图片描述

在这里插入图片描述

在src包下创建一个Main类,用来测试代码。

这个代码我们发现其中存在了大量的冗余代码,仔细分析, 我们发现 Animal 和 Cat 以及 Bird 这几个类中存在一定的关联关系:

  1. 这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的

  2. .这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.

  3. 从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a 语义),例如:猫是一个动物。

此时我们就可以让 Cat 和 Bird 分别继承 Animal 类, 来达到代码重用的效果,代码如下

在这里插入图片描述

  • 此时, Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类, 对于像 Cat 和 Bird 这样的类, 我们称为 子类, 派生类和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果.

两张图中的代码,都能达到代码效果

在这里插入图片描述

但图二相比图一来讲,通过使用继承特性达到代码的复用性,少写了许多冗余代码,使整个程序看上去更加简洁。

思考

要是我们将父类Animal中的属性namepublic改成private会怎样?

在这里插入图片描述

我们可以看到,改成private后不仅在Main文件中会报错,在子类Bird文件当中也会报错

在这里插入图片描述

因为private这个访问修饰符的特性是私有的,被private修饰的成员只能在本类中访问,也就是说,如果将父类Animal中的属性name改成private,此时name属性只能在父类中访问,而子类Brid就访问不到,所以会报错,由此得知

  • 子类会继承父类的所有 public 的字段和方法.

  • 对于父类的 private 的字段和方法, 子类中是无法访问的.

重点

子类在构造时, 一定要先帮助父类进行构造。在子类的构造方法中,我们要先调用父类的构造方法来帮助父类进行构造,不知道构造方法的同学可以先去我的这篇博客了解一下,链接: Java类和对象的学习.

如果父类当中是默认构造方法,那么子类当中的构造方法如果没主动调用父类构造方法的话,此时会显式调用父类默认的构造方法

在这里插入图片描述

如果父类实现了构造方法,那么此时子类在构造时就必须得使用super关键字调用父类的构造方法,不管子类当中有多少个构造方法,都必须在子类构造之前调用父类构造方法先帮助父类进行构造。

在这里插入图片描述

否则代码就会报错

在这里插入图片描述

super关键字后面有讲。

protected关键字


通过上面问题我们发现,使用public来修饰成员字段达不到"封装"的目的,而使用private子类又无法继承,那有没有两全其美的方法呢?

当然有,Java提供了一个protected访问权限,它的作用

  1. 实现了父类的 “封装” 性:对于类的调用者来说, protected 修饰的字段和方法是不能访问的

  2. 实现了子类的继承性:对于类的子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的

我们将public改成protected

在这里插入图片描述

此时可以发现,在Main文件当中执行代码依然会报错,而在子类Brid当中却没报错了。

在这里插入图片描述

protected其实是一个访问权限修饰符,而在Java当中一共有4种访问权限修饰符,分别有着不同的权限范围,以下一幅图可做参考

在这里插入图片描述

所以,我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public.

final关键字


final是Java中的一个关键字,被final修饰的变量表示常量 (不能被修改),而final其实还可以修饰类,当一个类被final修饰时,那么代表这个类不能被继承。

在刚才的继承学习当中我们知道,动物是一个类,而动物有很多种类,猫是动物的一种,但实际生活当中不管是猫还是鸟,其种类也是有很多种种类

在这里插入图片描述

在这里插入图片描述

此时子类可以进一步的再派生出新的子类,以此类推,将此叫做多层继承,但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了,防止继承层次,那么此时我们可以使用final关键字来进行限制。

示例

在这里插入图片描述

我们将ChineseGardenCat类使用final修饰,此时发现如果OrangeCat类去继承ChineseGardenCat则代码会报错。

在这里插入图片描述

方法重写


子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override),

构成重写的规则

  1. 方法名称相同

  2. 返回值相同

  3. 参数列表相同

先看以下代码

在这里插入图片描述

我们发现,在main方法中执行的b.func调用的是父类继承的方法

在这里插入图片描述

那么我们将子类B重写父类的func方法,会发生什么呢?

在这里插入图片描述

此时执行的是子类重写的方法。

在这里插入图片描述

针对重写的方法, 可以使用 @Override 注解来显式指定,有了这个注解能帮我们进行一些合法性校验.,例如不小心将方法名字写错了 (比如写成 fun), 那么此时编译器就会发现父类中没有 fun方法, 就会编译报错, 提示无法构成重写.在这里插入图片描述

重写的注意事项

  1. 普通方法可以重写, static 修饰的静态方法不能重写.
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210522220239134.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MjcwNzUx,size_16,color_FFFFFF,t_70)
  1. 重写中子类的方法的访问权限不能低于父类的方法访问权限.
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210522220404365.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MjcwNzUx,size_16,color_FFFFFF,t_70)
  1. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 协变类型除外).
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210522220518121.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MjcwNzUx,size_16,color_FFFFFF,t_70)  
如果重写方法返回值是继承关系,这种一般称之为协变类型,是可以构成重写的

super关键字


super 表示获取到父类实例的引用

super关键字的三种用法,类似于this关键字

  1. super.父类成员方法: 调用父类的成员方法

  2. super.父类成员变量: 调用父类的成员属性

  3. super(): 调用父类的构造方法

前面的代码中由于使用了重写机制, 调用到的是子类的方法. 如果需要在子类内部调用父类方法怎么办? 可以使用super 关键字。

在这里插入图片描述

此时调用子类的func方法的同时调用了父类的func方法

在这里插入图片描述

需要注意,如果子类不加super调用func方法,默认调用的是子类重写的func方法。

this关键字与super关键字的区别

尽管两个关键字的用法都差不多,但还是有本质区别的,可参考下图

在这里插入图片描述

继承小结


  1. 继承表示 is - a 语义,在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物.

  2. 子类在构造时要先帮助父类进行构造(重点)

  3. 在使用的时候应该尽可能的使用比较严格的访问权限

  4. 使用final 关键字的限制类被继承,防止多层继承,代码重构

  5. 重写中子类的方法的访问权限不能低于父类的方法访问权限

  6. 被static修饰的方法不能被重写

  7. 注意 super 和 this 功能有些相似, 但是还是要注意其中的区别.


多态

=================================================================

多态是同一个行为具有多个不同表现形式或形态的能力,就是同一个接口,使用不同的实例而执行不同操作,多态是一种思想。

在了解多态前,我们先来了解一下两个重要知识点,向上转型与动态绑定

向上转型


在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 “向上转型” , 表示往父类的方向转.

在这里插入图片描述

在Java中使用父类引用引用子类对象,此时就会发生向上转型,发生向上转型的时机有三种

  1. 直接赋值

  2. 方法传参

  3. 方法返回

这里先放上代码图,方便后面讲解。

在这里插入图片描述

直接赋值

在代码中,如果使用父类定义的变量引用子类创建的对象,此时就会发生向上转型,例如:

在这里插入图片描述

方法传参

在传给方法的参数中也可以发生向上转型的,例如:

在这里插入图片描述

方法返回值

不仅方法的参数可以发生向上转型,方法的返回者也可以发生向上转型,例如:

在这里插入图片描述

注意

发生向上转型后,通过父类的引用,只能访问自己(父类)的方法或属性。

在这里插入图片描述

如果通过父类去引用子类的方法,此时代码会发生报错

在这里插入图片描述

那么向上转型有什么作用呢?这个在后续的多态思想中很重要。

动态绑定


动态绑定也叫运行时绑定,发生在向上转型调用子类重写的方法的时候。我们学习了前面的向上转型,知道了通过父类的引用只能调用到父类的属性及方法,但如果子类重写了父类的方法,此时编译时调用的是父类的方法,但实际运行中调用的是子类重写的方法。

发生动态绑定的前提:

  1. 要先向上转型

  2. 通过父类引用来调用父类和子类同名的覆盖(重写)方法

举个例子

在这里插入图片描述

上图代码,在子类当中,重写了父类方法,此时在编译时调用的是父类的eat方法,但在运行时调用的是子类重写的eat方法

在这里插入图片描述

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值