1、继承
1.1 继承的概念
这里我们回顾下之前 Student 类,如果今天我们要定义一个 Teacher 类,我们会发现,这两个类有很多共同点:首先学生有姓名性别年龄,老师也有姓名性别年龄,同时学生的行为有上课,老师的行为也有上课这一行为,但是呢,学生与老师又有不同的点,比如学生有成绩,而老师有工资,再比如学生有考试的行为,而老师却有改试卷的行为,所以我们再一想,他们共同的点是不是可以抽取出来形成一个新的类?
继承(inheritance)机制:这个机制是面向对象程序设计中一个可以使代码复用的重要手段,他可以在原有类的特性上进行扩展,增加新的功能,由继承下来的类,称为派生类,从而实现代码复用,就比如我们上面的 Student 和 Teacher 类,抽出共同的部分,然后继承共同的部分,达到代码复用:
通过上图我们发现 Student 和 Teacher 类都继承了 Person 类的属性,其中 Person 称为父类/基类/超类,而 Student,Teacher 可以称为 Person 的子类/派生类,继承之后,子类可以复用父类中的成员,而子类只需要关心自己新增加的成员即可,同时也可以看出,继承最大的作用就是实现代码复用和实现多态(多态下期讲)
1.2 继承的语法和简单使用
现在我们是有了这样的一个思路,如何用代码实现呢?这里我们需要了解 extends 关键字,具体的语法如下:
修饰符 class A extends B { // ... }
而如上代码中,我们就可以理解成,A类继承了B类,所以A是B的子类,B是A的父类,那我们讲我们上图中画的图用代码实现一下:
public class Person { public String name; public String sex; public int age; //老师和学生都需要吃饭和上课 public void attendClass() { System.out.println(name + "正在上课"); } public void eat() { System.out.println(name + "正在吃饭"); } } class Student extends Person { //学生有成绩 public float score; //学生需要考试 public void examination() { System.out.println(name + "同学正在考试"); } } class Teacher extends Person { //老师有工资 public float salary; //老师需要改试卷 public void modifyPaper() { System.out.println(name + "老师正在改试卷"); } }
我们先分析下上面的代码:首先 Student 和 Teacher 分别继承了 Person 这个类,也就是说他们继承了父类的成员,所以子类中有两部分,第一部分是父类的成员,第二部分是子类自己的成员,简而言之,就是通过子类实例化的对象的成员里面包含了父类成员。
在一个我们可以观察下他们抽取出来的部分,确实是如我们刚开始所说,把学生和老师共同拥有的属性和行为抽取出来形成一个人的类,接下来我们就用main方法去测试下他们:
class Test { public static void main(String[] args) { Student student = new Student(); student.name = "张三"; student.examination(); Teacher teacher = new Teacher(); teacher.name = "李四"; teacher.modifyPaper(); student.eat(); teacher.eat(); } }
从这个打印结果也能证明我们前面的结论,子类实例化的对象确实继承了父类的属性和行为!
总结:
- 子类会将父类中的成员继承到子类中
- 子类继承父类后,必须要添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了
1.3 子类中访问父类成员出现同名
上面的演示我们也看到了,子类是可以访问父类的成员的(建立在限定符允许的情况,后面讲),那如果出现子类的成员变量和父类成员变量同名怎么办呢?如果成员方法也同名怎么办?
我们简单对上面的学生类做修改:
class Student extends Person { //学生有成绩 public float score; public String name = "123"; //学生需要考试 public void examination() { System.out.println(name + "同学正在考试"); } public void eat() { System.out.println("执行了子类的eat"); } } class Test { public static void main(String[] args) { Student student = new Student(); student.examination(); student.eat(); } }
这个代码里面的 name 变量和 eat 方法跟父类里的同名了:
如果同名执行的是父类的话,则打印的是:null同学正在考试 和 null正在吃饭 因为如果执行父类则 name 变量没有初始化,默认值是 null。
如果同名执行的是子类的话,则打印的是:123同学正在考试 和 执行了子类的eat 因为我们原地初始化了子类的 name 变量为"123",我们让main方法走起来:
总结:
- 如果子类成员变量和父类成员变量同名,则优先访问子类的
- 成员变量优先访问遵循就近原则,自己有优先自己的,自己没有则向父类中找,都没有则报错
- 如果子类成员方法和父类成员方法同名(没有构成重载),则优先访问子类的
- 如果子类成员方法和父类成员方法同名(构成了重载),则根据调用方法传递的参数选择合适的成员访问,都没有则报错
如果在子类中存在与父类相同的成员时,我非要访问父类的,怎么办?用super关键字,我们马上了解
2、 super关键字
2.1 如何使用 super 关键字
由于场景的需要,可能会出现父类和子类出现相同名字的成员,如果我们要在子类中访问相同名字的父类成员,直接访问是不可以做到的,这里就可以使用 super 关键字,我们把上面的代码稍作修改:
class Student extends Person { //学生有成绩 public float score; public String name = "123"; //学生需要考试 public void examination() { System.out.println(super.name + "同学正在考试"); } public void eat() { System.out.println("执行了子类的eat"); } public void test() { super.eat(); } } class Test { public static void main(String[] args) { Student student = new Student(); student.examination(); student.test(); } }
如果说 super 可以当子类和父类成员出现同名加上 super 会帮我们访问父类的话,则会打印 null同学正在考试 和 null正在吃饭 我们来看看是不是真的帮我们访问的父类成员:
注意:
- super 只能在非静态方法中使用,因为只有产生了对象才能访问实例成员变量和成员方法,而静态成员变量和静态成员方法是不依赖于对象的
- 在子类方法中,访问父类的成员变量或者成员方法
- super 调用构造方法后面讲
2.2 子类构造方法
之前我们了解过,当创建对象的时候,先要调用类的构造方法,当构造方法调用完毕,对象才真正产生了,那么在我们实例化子类的时候,子类既然要继承父类的属性,需先调用父类的构造方法,然后再执行子类的构造方法,所以当我们什么构造方法都没写的时候,编译器会默认提供无参的构造方法。
既然实例化子类的时候需要先调用父类的构造方法,所以编译器默认给子类提供的构造方法就是这样的:
public Student() { super(); }
也就是说,子类构造方法中,如果用户没有写,编译器会默认调用父类的无参构造方法: super();而且 super();必须是构造方法中的第一条语句,并且只能出现一次。
这里我们做个测试,证明没有写自己写 super();的时候编译器会默认调用:
public class Person { public String name; public String sex; public int age; public Person() { System.out.println("父类构造方法执行!"); } } class Student extends Person { public float score; public Student() { System.out.println("子类构造方法执行!"); } }
总结:子类虽然没有调用父类构造方法,但编译器还是默认带上了, 同时也正如我们所说,先调用父类在调用子类,因为子类对象中的成员有两部分组成,分别是父类继承下来的部分以及子类自己增加的部分,字面意思父子,肯定是现有父后有子,所以在子类实例化的时候,必须先调用父类的构造方法,将父类继承下来的成员构造完整之后,在调用子类的构造方法,将子类自己的成员初始化完整。
注意:
- 在子类构造方法中,编译器虽然隐含了super() 调用,但是如果父类构造方法是有参的,则需要子类构造方法里显式提供合适父类构造方法的调用
- super(...) 只能在子类构造方法出现一次,不能和 this 同时出现
2.3 super 和 this 的区别
这个其实也是面试会问到的一个题,下面我们就来看下他们的区别:
相同点:
- 都是Java中的关键字
- 都只能在类的非静态方法中使用,用来访问非静态成员和变量
- 在构造方法中出现的话,必须是构造方法中的第一条语句,而且他俩不能同时出现
不同点:
- this 是当前对象的引用,而 super 是从父类继承下来部分成员的引用,不能说是父类对象的引用,因为实例化子类的时候并没有创建父类的对象
- this 用来访问本类的方法和属性,super 用来访问父类的方法和属性(都是非静态)
- 在构造方法中,this 用来调用本类的构造方法,super 用来调用父类的构造方法
- 子类构造方法中,super 一定会出现,this 如果不写则没有
图示例: