class 子类 extends 父类 {
}
规则
-
extends是继承的关键字
-
Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
-
子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.
代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法),有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联,例如以下代码
在src内创建一个包testdome,在testdome创建了三个不同的文件,每个文件代表一个类
在src包下创建一个Main类,用来测试代码。
这个代码我们发现其中存在了大量的冗余代码,仔细分析, 我们发现 Animal 和 Cat 以及 Bird 这几个类中存在一定的关联关系:
-
这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的
-
.这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.
-
从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a 语义),例如:猫是一个动物。
此时我们就可以让 Cat 和 Bird 分别继承 Animal 类, 来达到代码重用的效果,代码如下
- 此时, Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类, 对于像 Cat 和 Bird 这样的类, 我们称为 子类, 派生类和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果.
两张图中的代码,都能达到代码效果
但图二相比图一来讲,通过使用继承特性达到代码的复用性,少写了许多冗余代码,使整个程序看上去更加简洁。
思考
要是我们将父类Animal中的属性name的public改成private会怎样?
我们可以看到,改成private后不仅在Main文件中会报错,在子类Bird文件当中也会报错
因为private这个访问修饰符的特性是私有的,被private修饰的成员只能在本类中访问,也就是说,如果将父类Animal中的属性name改成private,此时name属性只能在父类中访问,而子类Brid就访问不到,所以会报错,由此得知
-
子类会继承父类的所有 public 的字段和方法.
-
对于父类的 private 的字段和方法, 子类中是无法访问的.
重点
子类在构造时, 一定要先帮助父类进行构造。在子类的构造方法中,我们要先调用父类的构造方法来帮助父类进行构造,不知道构造方法的同学可以先去我的这篇博客了解一下,链接: Java类和对象的学习.
如果父类当中是默认构造方法,那么子类当中的构造方法如果没主动调用父类构造方法的话,此时会显式调用父类默认的构造方法
如果父类实现了构造方法,那么此时子类在构造时就必须得使用super关键字调用父类的构造方法,不管子类当中有多少个构造方法,都必须在子类构造之前调用父类构造方法先帮助父类进行构造。
否则代码就会报错
super关键字后面有讲。
通过上面问题我们发现,使用public来修饰成员字段达不到"封装"的目的,而使用private子类又无法继承,那有没有两全其美的方法呢?
当然有,Java提供了一个protected访问权限,它的作用
-
实现了父类的 “封装” 性:对于类的调用者来说, protected 修饰的字段和方法是不能访问的
-
实现了子类的继承性:对于类的子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
我们将public改成protected
此时可以发现,在Main文件当中执行代码依然会报错,而在子类Brid当中却没报错了。
protected其实是一个访问权限修饰符,而在Java当中一共有4种访问权限修饰符,分别有着不同的权限范围,以下一幅图可做参考
所以,我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public.
final是Java中的一个关键字,被final修饰的变量表示常量 (不能被修改),而final其实还可以修饰类,当一个类被final修饰时,那么代表这个类不能被继承。
在刚才的继承学习当中我们知道,动物是一个类,而动物有很多种类,猫是动物的一种,但实际生活当中不管是猫还是鸟,其种类也是有很多种种类
此时子类可以进一步的再派生出新的子类,以此类推,将此叫做多层继承,但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了,防止继承层次,那么此时我们可以使用final关键字来进行限制。
示例
我们将ChineseGardenCat类使用final修饰,此时发现如果OrangeCat类去继承ChineseGardenCat则代码会报错。
子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override),
构成重写的规则
-
方法名称相同
-
返回值相同
-
参数列表相同
先看以下代码
我们发现,在main方法中执行的b.func调用的是父类继承的方法
那么我们将子类B重写父类的func方法,会发生什么呢?
此时执行的是子类重写的方法。
针对重写的方法, 可以使用 @Override 注解来显式指定,有了这个注解能帮我们进行一些合法性校验.,例如不小心将方法名字写错了 (比如写成 fun), 那么此时编译器就会发现父类中没有 fun方法, 就会编译报错, 提示无法构成重写.
重写的注意事项
- 普通方法可以重写, 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)
- 重写中子类的方法的访问权限不能低于父类的方法访问权限.
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210522220404365.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MjcwNzUx,size_16,color_FFFFFF,t_70)
- 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 协变类型除外).
![在这里插入图片描述](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关键字的三种用法,类似于this关键字
-
super.父类成员方法: 调用父类的成员方法
-
super.父类成员变量: 调用父类的成员属性
-
super(): 调用父类的构造方法
前面的代码中由于使用了重写机制, 调用到的是子类的方法. 如果需要在子类内部调用父类方法怎么办? 可以使用super 关键字。
此时调用子类的func方法的同时调用了父类的func方法
需要注意,如果子类不加super调用func方法,默认调用的是子类重写的func方法。
this关键字与super关键字的区别
尽管两个关键字的用法都差不多,但还是有本质区别的,可参考下图
-
继承表示 is - a 语义,在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物.
-
子类在构造时要先帮助父类进行构造(重点)
-
在使用的时候应该尽可能的使用比较严格的访问权限
-
使用final 关键字的限制类被继承,防止多层继承,代码重构
-
重写中子类的方法的访问权限不能低于父类的方法访问权限
-
被static修饰的方法不能被重写
-
注意 super 和 this 功能有些相似, 但是还是要注意其中的区别.
=================================================================
多态是同一个行为具有多个不同表现形式或形态的能力,就是同一个接口,使用不同的实例而执行不同操作,多态是一种思想。
在了解多态前,我们先来了解一下两个重要知识点,向上转型与动态绑定
在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 “向上转型” , 表示往父类的方向转.
在Java中使用父类引用引用子类对象,此时就会发生向上转型,发生向上转型的时机有三种
-
直接赋值
-
方法传参
-
方法返回
这里先放上代码图,方便后面讲解。
直接赋值
在代码中,如果使用父类定义的变量引用子类创建的对象,此时就会发生向上转型,例如:
方法传参
在传给方法的参数中也可以发生向上转型的,例如:
方法返回值
不仅方法的参数可以发生向上转型,方法的返回者也可以发生向上转型,例如:
注意
发生向上转型后,通过父类的引用,只能访问自己(父类)的方法或属性。
如果通过父类去引用子类的方法,此时代码会发生报错
那么向上转型有什么作用呢?这个在后续的多态思想中很重要。
动态绑定也叫运行时绑定,发生在向上转型调用子类重写的方法的时候。我们学习了前面的向上转型,知道了通过父类的引用只能调用到父类的属性及方法,但如果子类重写了父类的方法,此时编译时调用的是父类的方法,但实际运行中调用的是子类重写的方法。
发生动态绑定的前提:
-
要先向上转型
-
通过父类引用来调用父类和子类同名的覆盖(重写)方法
举个例子
上图代码,在子类当中,重写了父类方法,此时在编译时调用的是父类的eat方法,但在运行时调用的是子类重写的eat方法
坑