文章目录
5.继承
extends
为什么需要继承
一个小问题, 还是看个程序com.zzw.extend_包 Extends01.java
, 提出代码复用的问题.
我们编写两个类, 一个是Pupil [小学生], 一个是Graduate [大学毕业生].
问题: 两个类的属性和方法有很多是相同的, 怎么办?
⇒ 继承 (代码复用性~)
继承基本介绍和示意图
继承可以解决代码复用, 让我们的编程更加靠近人类思维. 当多个类存在相同的属性(变量)和方法时, 可以从这些类中抽象出父类, 在父类中定义这些相同的属性和方法, 所有的子类不需要重新定义这些属性和方法, 只需要通过extends来声明继承父类即可.
画出继承的示意图
继承的基本语法
class 子类 extends 父类 {
}
1.子类就会自动拥有父类定义的属性和方法
2.父类又叫 超类, 基类
3.子类又叫派生类.
快速入门案例
我们对Extends01.java
进行改进, 使用集成的方法, 请大家注意体会集成的好处 com.zzw.extend_.improve_ 包
继承给编程带来的便利
1.代码的复用性提高了
2.代码的扩展性和维护性提高了
继承的深入讨论
●继承的深入讨论/细节问题 com.zzw.extend_包 Base.java Sub.java ExtendsDetail.java
public class Base { //父类
//4个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public Base() { //无参构造器
System.out.println("父类Base()构造器被调用....");
}
public Base(String name) { //有参构造器
System.out.println("父类Base(String name)构造器被调用...");
}
public Base(String name, int age) { //有参构造器
System.out.println("父类Base(String name, int age)构造器被调用...");
}
public void test100() {
System.out.println("test100");
}
protected void test200() {
System.out.println("test200");
}
void test300() {
System.out.println("test300");
}
private void test400() {
System.out.println("test400");
}
}
public class Sub extends Base { //子类
public Sub() {//子类无参构造器
System.out.println("子类Sub()构造器被调用...");
}
public Sub(String name) { //有参构造器
System.out.println("子类Sub(String name)构造器被调用...");
}
public Sub(String name, int age) { //有参构造器
System.out.println("子类Sub(String name, int age)构造器被调用...");
}
public void sayOk() { //子类方法
}
}
1.子类继承了父类所有的属性和方法, 非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问, 要通过父类提供公共的方法去访问.
debug工具: 验证子类的确是继承了父类所有的属性
2.子类必须调用父类的构造器, 完成父类的初始化
3.当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下都会去调用父类的无参构造器, 如果父类没有提供无参构造器, 则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作, 否则, 编译不会通过.
4.如果希望指定去调用父类的某个构造器, 则显示地调用一下: super(参数列表)
5.super在使用时, 必须放在构造器第一行(super只能在构造器中使用)
6.super() 和 this() 都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器
7.java所有类都是Object类的子类, Object 是所有类的基类.
Ctrl + H 可以查看类的继承关系
8.父类构造器的调用不限于直接父类! 将一直往上追溯直到Object类(顶级父类)
父类的对象并没有消失或离开,而是在内存中按照类的继承层次结构被创建并初始化。在构造函数调用完成后,对象的状态会保存在内存中,它可以通过引用被访问和操作。
9.子类最多只能继承一个父类(指直接继承), 即java是单继承机制
思考: 如何让A类继承B类和C类? A 继承 B, B 继承 C
10.不能滥用继承, 子类和父类之间必须满足 is-a 的逻辑关系
Person is a Music?
Person Music
Music extends Person //不合理
Animal
Cat extends Animal //合理
继承本质分析
√ 案例
我们看一个案例来分析当子类继承父类, 创建子类对象时, 内存中到底发生了什么? 老师提醒: 当子类对象创建好后, 建立查找的关系. com.zzw.extend_包 ExtendsTheory.java
√ 子类的内存布局
com.zzw.extend_包 ExtendsTheory.java
//讲解继承的本质
public class ExtendsTheory {
public static void main(String[] args) {
son son = new son(); //内存的布局
//?-> 这时请大家注意, 要按照查找关系来返回信息
//(1) 首先看子类是否有该属性
//(2) 如果子类有这个属性, 并且可以访问, 则返回信息
//(3) 如果子类没有这个属性, 就看父类有没有这个属性(如果父类有该属性, 并且可以访问, 就返回信息..)
//(4) 如果父类没有就按照(3)的规则, 继续找上级父类, 直到Object.
System.out.println(son.name);//返回的就是大头儿子
//System.out.println(son.age);//返回的就是39
System.out.println(son.getAge());//返回的就是39
System.out.println(son.hobby);//返回的就是旅游
}
}
class GrandPa { //爷爷类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa{ //父类
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class son extends Father { //子类
String name = "大头儿子";
}
课堂练习
com.zzw.extend_exercise 包
案例1 ExtendsExercise01.java
class A {
A() {
System.out.println(“a”);
}
A(String name) {
System.out.println(“a name”);
}
}
class B extends A {
B() {
this(“abc”);
System.out.println(“b”);
}
B(String name) {
System.out.println(“b name”);
}
}
main中: B b = new B(); 会输出什么?
案例2 ExtendsExercise02.java
class AA { //AA类
public AA() {
System.out.println(“我是AA类”);
}
}
class BB extends AA{ //BB类, 继承AA类
public BB() {
System.out.println(“我是BB类的无参构造”);
}
public BB(String name) {
System.out.println(name + “我是BB类的有参构造”);
}
}
class CC extends BB { //CC类, 继承 BB类
public CC() {
this(“hello”);
System.out.println(“我是CC类的无参构造”);
}
public CC(String name) {
super(“hahah”);
System.out.println(“我是CC类的有参构造”);
}
}
main中: CC cc = new CC(); 会输出什么?
案例3 ExtendsExercise03.java
编写Computer类, 包含CPU, 内存, 硬盘等属性, getDetails方法用于返回Computer的详细信息.
编写PC子类, 继承Computer类, 添加特有属性 【品牌brand】
编写NotePad子类, 继承Computer类, 添加特有属性【颜色color】
编写ExtendsExercise03类, 在main方法中创建PC和NotePad对象, 分别给对象中特有的属性赋值, 以及从Computer类继承的属性赋值, 并使用方法打印输出信息.
6.super关键字
基本介绍
super代表父类的引用, 用于访问父类的属性, 方法, 构造器
基本语法
1.访问父类的属性, 但不能访问父类的private属性
super.属性名;
2.访问父类的方法, 但不能访问父类的private方法
super.方法名(参数列表);
3. 访问父类的构造器(这点前面讲过)
super(参数列表); 只能放在构造器的第一句, 只能出现一句!
super给编程带来的便利/细节
com.zzw.super_包 Super01.java
1.调用父类的构造器的好处 (分工明确, 父类属性由父类初始化, 子类的属性由子类初始化)
2.当子类中有和父类中的成员 (属性和方法) 重名时, 为了访问父类的成员, 必须通过super. 如果没有重名, 使用 super, this 和 直接访问 是一样的效果!
3.super的访问不限于直接父类, 如果爷爷类和本类中有同名的成员, 也可以使用super去访问爷爷类的成员; 如果多个基类(上级类)中都有同名的成员, 使用super访问遵循就近原则. A -> B -> C, 当然也需要遵守访问权限的相关规则
super和this比较
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类的属性, 如果本类没有此属性则从父类继续查找 | 从父类开始查找属性 |
2 | 调用方法 | 访问本类中的方法, 如果本类没有此方法则从父类继续查找 | 从父类开始查找方法 |
3 | 调用构造器 | 调用本类构造器, 必须放在构造器的首行 | 调用父类构造器, 必须放在子类构造器的首行 |
4 | 特殊 | 表示当前对象 | 子类中访问父类对象 (父类的一个引用) |
●学习完继承后, 类定义进一步完善
7.方法重写/覆盖
基本介绍
简单地说: 方法覆盖(重写)就是子类有一个方法, 和父类的某个方法的名称, 返回类型, 形参列表一样, 那么我们就说子类的这个方法覆盖了父类的那个方法.
快速入门
com.zzw.override_包 Override01.java
注意事项和使用细节
方法重写也叫方法覆盖, 需要满足下面的条件 OverrideDetail.java[看Animal和Dog]
1.子类方法的形参列表, 方法名称, 要和父类方法的形参列表, 方法名称完全一样.
2.子类方法的返回类型和父类方法返回类型一样, 或者是父类返回类型的子类. 比如 父类 返回类型是 Object, 子类方法返回类型是String. 否则, attempting to use incompatible return type
3.子类方法不能缩小父类方法的访问权限. 否则, attempting to assign weaker access privileges
课堂练习
√ 题1
请对方法的重写和重载做一个比较
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload) | 本类 | 必须一样 | 类型, 个数或者顺序至少有一个不同 | 没有要求 | 没有要求 |
重写(override) | 父子类 | 必须一样 | 必须一样 | 子类重写的方法, 返回的类型和父类方法返回的类型一致 或是其子类 | 子类不能缩小父类方法的访问权限 |
√ 题2
OverrideExercise.java
1.编写一个Person类, 包括属性/private (name, age), 构造器, 方法say(返回自我介绍的字符串)
2.编写一个Student类, 继承Person类, 增加id, score属性/private, 以及构造器, 定义say方法(返回自我介绍的信息)
3.在main中, 分别创建Person 和 Student对象, 调用say方法输出自我介绍.