“继承”是面上对象中的第二特征,体现了类与类之间的“is-a”关系。当两个类进行继承关联绑定的时候,子类自动具备来自于父类的属性和行为,做到代码的复用和设计的分离。
Java继承的机制
类Object
Class Object is the root of the class hierarchy.Every class has Object as a superclass.All Objects,includeing arrays,implement the methods of this class。
类Object是类层次结构的根类。每个类都使用Object作为超类。所有对象(包括数据)都实现这个类的方法。
JDKDOC文档很明确的告诉我们:Object类在整个java的类继承体系中是处于绝对顶层的位置。它是所有类的根、是祖宗。很多对象上额外对出来的方法就来自与它。由此可见,类Object的所有方法都是共有性最强的方法,只要是Java对象就肯定具备这些行为,这方法的重要性就不言而喻了。
方法名 | 概括 | 相关学习 |
finalize | 销毁对象的方法,由GC调用 | 垃圾回收 |
equals | 判断两个对象“业务”是否相等 | 方法重写 |
toString | 返回对象的字符串描述 | 方法重写 |
hashCode | 返回对象的哈希码值 | 集合框架 |
wait | 导致当前线程等待 | 线程 |
notify | 唤醒等待的单个线程 | 线程 |
notifyAll | 唤醒等待的所有的线程 | 线程 |
getClass | 获取实例对象所属类型的类模板对象 | 反射 |
clone | 克隆并返回当前对象的一个新的副本对象 | 原型模式 |
关键字extends
public class 子类 extends 父类 {
//子类的内容
}
- 类SubClass的父类是类SuperClass,而类SuperClass的父类是默认的类Object;所以通过继承的传递性,SuperClass具备来自Object的所有数据和行为,SubClass具备来自于SuperClass的所有数据和行为,所以SubClass也具有来自Object的数据和行为;
- “extends”的英文本意是“扩展”,也就是说子类在父类的基础上是可以有变化性的。这种变化性体现为:增加子类新的属性和新的行为,或修改继承而来的行为(属性不能改!);
单继承
在默认情况下我们书写的一个Java类会自动继承Object类;如果使用”extends”关键字,我们也可以为它指定一个其它父类。那么,接下来的问题是“一个类可以指定几个父类呢”❓
在面向对象思维中只提到了有“继承”这种特性。但具体是“每个类可以同时继承多个父类(多继承)”还是“每个类只能有一个父类(单继承)”却并没有说明。所以现实当中,不同的面向对象的编程语言各有各的语法设计,而Java采用的是单继承。
首先,无论“多继承”还是“单继承”各有优劣;
优点 | 缺点 |
可以同时具备来自多父类的特征,让子类具有更大的丰富度 | 如果多个父类具有相同的特征,那么子类到底继承的是哪一个的?这会带来设计上的混乱。 |
继承机构会变得非常复杂,可能呈现出网状的结构。这不利于程序设计的清晰度,增加了复杂性。 |
优点 | 缺点 |
类继承的层次结构非常清晰,设计上更容易把握住父类具有唯一共性 | 设计的丰富度会降低 |
当然,放弃了多继承,不代表Java的表现力被弱化了。Java设计了其它语法来补充这种丰富度。比如:大家后面学习到的“接口”、“内部类”,都可以达到多继承同样的效果,却不需要在无谓的复杂度上做无谓的困扰。
属性在继承中的表现
结论:父类中的的属性 在继承的情况,是肯定被放入到了子类对象当中。只是由于访问修饰符的限制,导致某些特殊访问修饰符的属性在子类中不能被直接访问。
访问修饰符
在Java当中一个提供了三个关键字,四种情况来表现访问修饰符:
本类 同包非子类 同包子类 非同包子类 非同包非子类
MySelf MyWife MySon MyDaughter Stranger
public 公共 能 能 能 能 能
protected 受保护 能 能 能 能
不写(默认) 同包 能 能 能
private 私有 能
在这里看到,对于父类当中的私有属性,子类是看不看,但不代表它没有被继承。父类当中的同包属性,非同包子类是看不见,同样不代表它没有被继承
Java继承的模型
我们在学习基本的类与对象章节的时候就提到过:当代码中出现new某个类构造方法的时候,就会在内存的堆区中产生一个该类型的对象。
在这个我们提出Java当中实现继承的本质----“内存叠加”来帮助理解:
- 当我们new一个子类的构造方法时,程序流程会进入到该子类构造方法中;
- 子类构造方法的第一句会有默认的调用其父类的构造方法(这里涉及到super()的知识点),程序流程转入到父类构造方法中;
- 执行完父类的构造方法,就会在内存里面产生父类对象,然后再返回到子类构造代码中继续执行;
- 执行完子类的构造方法后,会在刚才产生的父类对象的下面叠加产生子类对象的部分。这两部分合在一起构成一个既拥有父类内容又拥有子类内容的完整子类对象;
- 最后把这个完整的子类对象的引用赋值给引用类型的变量。
构造方法
父类的构造方法不会被子类继承! 而且构造方法不能被重写! (切记!)
this() | super() | |
作用: | 调用本类的其他构造方法;从而达到本类构造方法的代码可以复用的效果。与继承无关。 | 调用父类的指定构造方法;从而达到使用指定的父类构造方法构造子类中的父类对象部分。与继承无关。 |
位置: | 构造方法的第一句;与super()相互排斥。 | 构造方法的第一句;与this()相互排斥。 |
默认: | 没有 | 有。就是无参的super() |
父类的属性会被子类继承,无论访问修饰符!
在继承机制中已经讲过,产生子类对象会先产生父类对象部分,然后叠加子类特有部分,从而构成完整子类对象。所以父类的属性都会放入到父类对象部分,那么也就在完整子类对象中。
方法
父类的方法会被子类继承,无论访问修饰符!
子类可以拥有父类的同名方法,符合重写规范的话会发生覆盖。
- 方法名相同;
- 参数列表相同(包括参数个数、类型、顺序三者都相同);
- 返回类型相同;
- 子类方法的访问修饰符不能小于父类方法的访问修饰符;
- 子类方法的方法声明不能比父类方法的方法声明抛出更多的异常。
只有这5点均满足的情况下,才会发生重写;否则会报错或两个方法共存(重载)。
this. | super. | |
位置: | 类的构造方法或普通方法中均可书写; | 类的构造方法或普通方法中均可书写; |
作用: | 访问本类定义的所有属性和行为,以及从父类继承而来且被访问修饰符允许的属性或行为; | 访问从父类继承而来且被访问修饰符允许的属性或行为; |
含义: | 当前对象,生活化寓意就是“我”; | 当前对象的父类对象部分,生活化寓意是“我爹”; |
final---不仅仅是用来声明常量
这个关键字的含义为:最终的、不变的。所以用final修饰的内容都是具有最终不变的特点。
1、final修饰的变量(包括属性 和 局部变量,甚至是形参) 就是成为常量
int a = 10; //a是变量,值可以变
final int b = 100;//b是常量,值不能变
2、final可以修饰方法,由final修饰的方法不能被“重写”。 记住:是不能重写,不是不能“重载”。重写才是对原方法进行实现部分的改变,重载是添加了一个(或多个)新的重名方法。
用final修饰的方法 由于不能被重写,所以我们叫它“最终方法”/“终态方法”。
3、final可以修饰类, 这种类不能被继承。 我们可以把子类看成是对父类的改变,用了父类的属性和行为,还可以自己加新的属性和行为,也能把父类的行为进行重写改变。所以如果一个类被定义为final,不变的,那么它就不能够再生成新的子类。也就是说它不能当爸爸了,成为继承结构树上的某个分支的最终节点。这样的类,称为“终态类”或“最终类”。
4、final不能修饰构造方法。因为构造方法本身就不能被继承,当然也就不能被重写,所以也不存在被改变的情况。