【java面向对象进阶】------继承

news/2025/3/19 9:27:20/

1. 继承概述

格式:
在这里插入图片描述

1.1 引例

假如我们要定义如下类: 学生类,老师类和工人类,分析如下。

  1. 学生类 属性:姓名,年龄 行为:吃饭,睡觉

  2. 老师类 属性:姓名,年龄,薪水 行为:吃饭,睡觉,教书

  3. 班主任 属性:姓名,年龄,薪水 行为:吃饭,睡觉,管理

如果我们定义了这三个类去开发一个系统,那么这三个类中就存在大量重复的信息(属性:姓名,年龄。行为:吃饭,睡觉)。

这样就导致了相同代码大量重复,代码显得很臃肿和冗余,那么如何解决呢?

假如多个类中存在相同属性和行为时,我们可以将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。

如图所示:
在这里插入图片描述

其中,多个类可以称为子类,单独被继承的那一个类称为父类超类(superclass)或者基类

1.2 继承的含义

继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。

例如,兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

继承:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性相同的行为。子类可以直接访问父类中的非私有的属性和行为。

继承的本质就是类跟类之间的父子关系
在这里插入图片描述

1.3使用继承的场景

在这里插入图片描述

1.4继承的好处

在这里插入图片描述

2.继承的特点

java_45">2.1java只支持单继承多层继承,不支持多继承

  • Java 只能单继承:一个类只能继承一个直接父类,不能同时继承多个类。
  • Java 不支持多继承:一个类不能继承多个父类,多个父类可能会有相同的方法,导致菱形继承问题(方法冲突),Java 采用接口来替代多继承。
  • Java 支持多层继承:即子类可以继承父类,父类又可以继承更高级的父类,形成继承链,最终所有类都会追溯到 Object 类。
  • Java 中所有的类都直接或间接继承 ObjectObject 是 Java 继承体系的顶级父类,所有类默认继承 Object,所以它提供的方法(如toString()equals()hashCode() 等)可以被所有类使用或重写。

2.2单继承

在这里插入图片描述

2.3不支持多继承

在这里插入图片描述
不支持多继承的原因:菱形继承问题

  1. 什么是菱形继承? 当一个子类同时继承两个父类,而这两个父类又继承自同一个祖先类时,就会出现继承路径交叉,形成菱形结构。

  2. 菱形继承带来的问题

  • 方法冲突:如果祖先类中有一个方法,两个父类都继承并可能重写了它,子类在调用这个方法时,不知道该继承哪一个父类的实现。
  • 数据冗余:如果祖先类中有成员变量,而两个父类各自拷贝了一份,子类就会持有两份相同的数据,造成数据不一致的风险。

2.4多层继承

在这里插入图片描述

2.5继承体系

Java 中所有的类都直接或间接继承 Object

  • 在 Java 中,所有的类默认继承 Object 类,即使没有显式声明 extends Object,编译器也会自动添加。
    在这里插入图片描述

在一个继承体系中,针对任意一个类:
它可以使用 直接父类 中的内容

在这里插入图片描述
也可以使用 间接父类 中的内容
在这里插入图片描述
但是不能使用 如下图这种 类似叔叔关系的类 中的内容
在这里插入图片描述

2.6继承体系设计的技巧

2.6.1画图法:

画图:从下往上画

  • 下面:子类
  • 上面:父类
    需要把子类中的共性内容抽取到父类中

核心:

  • 共性内容抽取
  • 子类是父类中的一种

书写代码:

  • 从上往下写

2.6.2实例练习1

在这里插入图片描述

2.6.2.1体系设计

在这里插入图片描述

2.6.2.2代码实现

在这里插入图片描述
1.间接父类 Animal类

java">package a00extend;//动物类
public class Animal {public void eat(){System.out.println("吃东西");}public void drink(){System.out.println("喝水");}}

2.直接父类cat类

java">package a00extend;//猫类
public class cat extends Animal{public void catchMouse(){System.out.println("猫抓老鼠");}
}

3.直接父类dog类

java">package a00extend;
//狗类
public class dog extends Animal{public void lookHome(){System.out.println("狗看家");}
}

4.子类LiHua类

java">package a00extend;//狸花猫类
public class LiHua extends cat{}

5.子类Ragdoll类

java">package a00extend;//布偶猫类
public class Ragdoll extends cat{}

6.子类Husky 类

java">package a00extend;public class Husky extends dog{public void breakHome(){System.out.println("哈士奇在拆家");}
}

7.子类Teddy类

java">package a00extend;//泰迪类
public class Teddy {public void touch() {System.out.println("泰迪在蹭一蹭");}
}

8.Test测试类

java">package a00extend;public class Test {public static void main(String[] args) {//创建对象并调用方法//创建布偶猫 对象cat cat1 = new Ragdoll();cat1.eat();//来自间接父类Animalcat1.drink();//来自间接父类Animalcat1.catchMouse();//来自直接父类catSystem.out.println("-----------------------");//创建哈士奇 对象Husky dog1 = new Husky();dog1.eat();//来自间接父类Animaldog1.drink();//来自间接父类Animaldog1.lookHome();//来自直接父类dogdog1.breakHome();//来自本类Husky}
}

在这里插入图片描述

2.6.3实例练习2

2.6.3.1案例说明

请使用继承定义以下类:

  1. 学生类
    属性:姓名,年龄
    行为:吃饭,睡觉
  2. 老师类
    属性:姓名,年龄,薪水
    行为:吃饭,睡觉,教书
  3. 班主任
    属性:姓名,年龄,薪水
    行为:吃饭,睡觉,管理
2.6.3.2体系设计

老师类,学生类,还有班主任类,实际上都是属于人类的,我们可以定义一个人类,把他们相同的属性和行为都定义在人类中,然后继承人类即可,子类特有的属性和行为就定义在子类中了。

如下图

2.6.3.3代码实现

在这里插入图片描述

1.父类Human类

java">package a000extend;public class Human {// 合理隐藏private String name ;private int age ;// 合理暴露public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

2.子类Teacher类

java">package a000extend;public class Teacher extends Human {// 工资private double salary ;// 特有方法public void teach(){System.out.println("老师在认真教技术!");}public double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}
}

3.子类Student类

java">package a000extend;public class Student extends Human{}

4.子类BanZhuren类

java">package a000extend;public class BanZhuRen extends Human {// 工资private double salary ;// 特有方法public void admin(){System.out.println("班主任强调纪律问题!");}public double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}
}

5.测试类

java">package a000extend;public class Test {public static void main(String[] args) {Teacher dlei = new Teacher();dlei.setName("播仔");dlei.setAge(31);dlei.setSalary(1000.99);System.out.println(dlei.getName());System.out.println(dlei.getAge());System.out.println(dlei.getSalary());dlei.teach();System.out.println("-------------------");BanZhuRen linTao = new BanZhuRen();linTao.setName("灵涛");linTao.setAge(28);linTao.setSalary(1000.99);System.out.println(linTao.getName());System.out.println(linTao.getAge());System.out.println(linTao.getSalary());linTao.admin();System.out.println("-------------------");Student xugan = new Student();xugan.setName("播仔");xugan.setAge(31);//xugan.setSalary(1000.99);//xugan没有薪水属性,报错!System.out.println(xugan.getName());System.out.println(xugan.getAge());}
}

在这里插入图片描述

2.7子类访问的注意事项

子类不能直接访问父类的私有 (private) 方法和属性,只能访问非私有(public、protected、默认访问权限)的成员

3.子类能继承的内容

并不是父类的所有内容都可以给子类继承的, 值得注意的是子类可以继承父类的私有成员(当然这里是以内存角度进行思考,存在一定争议),只是子类无法直接访问而已,可以通过public修饰的getter/setter方法访问父类的private成员变量。
在这里插入图片描述

3.1 子类不能继承父类的构造方法

原因:

  • 命名冲突:如果子类继承了父类的构造方法,构造方法名将与子类类名不一致,违反了构造方法的命名规则。
  • 初始化责任:子类需要初始化自己的成员变量,而父类的构造方法无法直接处理子类的成员变量。

代码示例:
在这里插入图片描述

父类:

java">package a01extend;
//构造方法不能被 子类 继承
public class Fu {String name;int age;//无参构造public Fu() {}//带参构造public Fu(String name, int age) {this.name = name;this.age = age;}
}

子类:

java">package a01extend;public class Zi extends Fu{//如果一个类中没有构造方法,虚拟机会自动添加一个默认的空参构造
}

测试类:

java">package a01extend;public class Test {// 1. 使用空参构造创建子类对象// 由于子类 Zi 有空参构造方法(无论是否显式定义),可以直接创建对象Zi zi = new Zi();// 2. 尝试使用带参构造创建子类对象(代码会报错)// 原因:子类 Zi 没有定义带参构造方法,且子类不会继承父类的带参构造方法// 因此,无法通过传递参数的方式创建子类对象// a01extend.Zi z2 = new a01extend.Zi("小明", 19); // 这行代码会报错
}

3.2 成员变量

子类可以继承父类中的非private变量,并可以直接赋值调用

关于子类与父类中的private变量之间的继承关系 略有争议(本篇暂时以内存角度进行解释): 即子类确实继承了父类的 private 成员变量,但 无法直接赋值调用 它们;换句话说,private 成员变量在子类的对象中是存在的,但子类没有权限直接访问它们。

  • 内存角度:
    子类对象在内存中确实包含了父类的private变量。
    这是对象模型的实现细节,但并不意味着子类可以直接访问这些变量。
  • 语义角度:
    子类不会继承父类的private成员变量,因为这违反了封装性原则。
    父类的private变量是隐藏的,子类只能通过父类提供的公共方法间接访问。
    这种设计保护了父类的内部实现细节,使得父类可以自由地修改其实现而不会影响子类。

3.2.1成员变量的继承内存图

3.2.1.1无private修饰的成员变量

代码图解:

  • student类的字节码文件加载到方法区中

在这里插入图片描述

  • main()方法进栈

在这里插入图片描述

  • 创建对象的过程中:
  • 发现用到了Zi类,故Zi类的字节码文件加载到方法区中
  • 加载过程中发现Zi类 继承自Fu类 故将Fu类的字节码文件加载到方法区中
  • (当然所有的类都 继承一个 Object类)它对应的字节码文件也会加载到方法区中
    在这里插入图片描述
  • 注意创建对象的过程中:
  • 会将堆中开辟的一块内存一分为二:一部分记录继承自父类的成员变量,一部分记录本类中的成员变量
    在这里插入图片描述
  • 打印对象变量z的值
    在这里插入图片描述
  • 为对象的成员变量进行赋值:
  • 寻找过程是:先寻找本类中的成员变量,如果没有再寻找继承自父类的成员变量
    在这里插入图片描述
  • 打印对象的成员变量值
    在这里插入图片描述
  • main()方法出栈
    在这里插入图片描述
  • 堆中开辟的空间变成垃圾
    在这里插入图片描述
3.2.1.2有private修饰的成员变量

代码图解:

  • student类的字节码文件加载到方法区中

在这里插入图片描述

  • main()方法进栈

在这里插入图片描述

  • 创建对象的过程中:
  • 发现用到了Zi类,故Zi类的字节码文件加载到方法区中
  • 加载过程中发现Zi类 继承自Fu类 故将Fu类的字节码文件加载到方法区中
  • (当然所有的类都 继承一个 Object类)它对应的字节码文件也会加载到方法区中

在这里插入图片描述

  • 注意创建对象的过程中:
  • 会将堆中开辟的一块内存一分为二:一部分记录继承自父类的成员变量,一部分记录本类中的成员变量(当然这里的private成员变量也是被记录的)

在这里插入图片描述

  • 打印对象变量z的值

在这里插入图片描述

  • 由于用private修饰的成员变量都需要set和get方法进行赋值调用,这里是不能直接进行赋值调用的
  • 所以这里的赋值操作是无法成功的(代码也会报错)

在这里插入图片描述

  • 为子类本身具有的成员变量进行赋值
    在这里插入图片描述
  • 打印子类本身具有的成员变量
    在这里插入图片描述
  • 代码执行完毕,main方法出栈,同时对象变成垃圾

在这里插入图片描述

3.3成员方法

只有父类中的虚方法才能被子类继承;注意非static,非private,非final方法是不存储在虚方法表中的
在这里插入图片描述

3.3.1成员方法的继承内存图

代码示例:

  • student类字节码文件加载到方法区

在这里插入图片描述

  • main方法进栈

在这里插入图片描述

  • 在创建子类对象的时候,用到了Zi类,故子类的字节码文件加载到了方法区
  • 加载子的同时,发现Zi类继承自Fu类,于是Fu类的字节码文件也被加载到方法区
  • 由于任意一个类都继承了Object,于是Object类的字节码文件也被加载进方法区
  • 注意这里的虚方法表的继承过程

在这里插入图片描述

  • class文件加载完毕后,进行对象的创建
  • 注意堆中开辟的对象空间:一部分存储继承自父类的成员变量,一部分存储自身类中的成员变量(由于本例没有成员变量的书写,所以空间是空的)
    在这里插入图片描述
  • 打印对象变量的存储内容

在这里插入图片描述

  • 通过对象名来调用zishow方法:
  • 判断当前调用的方法是虚方法;于是从虚方法表中调用该方法,zishow方法进栈
    在这里插入图片描述
  • 通过对象名来调用Fushow1方法:
  • 判断当前调用的方法是虚方法;于是从虚方法表中调用该方法,Fushow1方法进栈
    在这里插入图片描述
    在这里插入图片描述
  • 通过对象名来调用Fushow2方法:
  • 判断当前调用的方法不是虚方法;
  • 于是先在本类中查找,发现没有Fushow2方法;
  • 然后在父类中查找,发现该方法被private修饰,无法直接调用(于是代码报错)
    在这里插入图片描述

4.继承后的特点

4.1成员变量

4.1.1成员变量的访问特点:

直接调用满足就近原则(谁离我近,我就用谁)
在这里插入图片描述

引例分析:
java">public class Fu {String name = "Fu"; // 父类的成员变量
}
java">
public class Zi extends Fu {String name = "Zi"; // 子类的成员变量,隐藏了父类的 namepublic void zishow() {String name = "zishow"; // 局部变量,隐藏了子类的 nameSystem.out.println(name); // 输出局部变量 name 的值}
}
代码解析:
  1. 成员变量隐藏

    • 子类 Zi 中定义了与父类 Fu 同名的成员变量 name,这会导致父类的 name 被隐藏。
    • 在子类中直接访问 name 时,访问的是子类的 name
  2. 局部变量隐藏

    • zishow 方法中,定义了一个局部变量 name,这会隐藏子类的成员变量 name
    • zishow 方法中访问 name 时,访问的是局部变量 name
  3. 就近原则

    • 当存在同名变量时,Java 会优先访问 最近作用域 的变量。
    • zishow 方法中,name 的访问顺序是:
      • 局部变量 name(最近)
      • 子类成员变量 name
      • 父类成员变量 name
输出结果:
  • 调用 zishow 方法时,输出的是局部变量 name 的值,即 "zishow"

4.1.2 变量同名时的访问规则

  • 成员变量隐藏

    • 子类中定义了与父类同名的成员变量时,父类的成员变量会被隐藏。
    • 如果需要访问父类的成员变量,可以使用 super 关键字,例如 super.name
  • 局部变量隐藏

    • 局部变量会隐藏同名的成员变量。
    • 如果需要访问被隐藏的成员变量,可以使用 this 关键字,例如 this.name

在这里插入图片描述

示例代码:

在这里插入图片描述

java">package a0000extend;public class Fu {String name ="Fu";
}
java">package a0000extend;public class Zi extends Fu{String name="Zi";public void show(){String name="ZiShow";System.out.println(name);//输出ZiShowSystem.out.println(this.name);//输出ZiSystem.out.println(super.name);//输出Fu}
}
java">package a0000extend;public class test {public static void main(String[] args) {Zi r1=new Zi();r1.show();}
}

在这里插入图片描述


小结:子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super 关键字,修饰父类成员变量,类似于之前学过的 this

需要注意的是:super代表的是父类对象的引用,this代表的是当前对象的引用

注意:Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员变量呢?对!可以在父类中提供公共的getXxx方法和setXxx方法。

4.2成员方法

4.2.1 成员方法的访问特点:

直接调用满足就近原则(谁离我近,我就用谁)

在这里插入图片描述

引例分析:

在这里插入图片描述

java">package a011extends;public  class test {public static void main(String[] args) {Zi z = new Zi();//子类中没有show方法,但是可以找到父类方法去执行z.show();z.show2();}
}
java">package a011extends;public class Zi extends Fu {public void show2() {System.out.println("Zi类中的show2方法执行");}
}
java">package a011extends;public  class test {public static void main(String[] args) {Zi z = new Zi();//子类中没有show方法,但是可以找到父类方法去执行z.show();z.show2();}
}

在这里插入图片描述

4.2.2 成员方法重名

如果子类父类中出现重名的成员方法(注意这里子类方法要进行 方法重写),则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。

引例:

在这里插入图片描述

java">package com.example;public class Fu {public void show() {System.out.println("Fu show");}
}
java">package com.example;public class Zi extends Fu {// 子类重写了父类的show方法@Overridepublic void show() {System.out.println("Zi show");}
}
java">package com.example;public class test {public static void main(String[] args) {Zi z = new Zi();// 子类中有show方法,只执行重写后的show方法z.show();  // 输出: Zi show}}

在这里插入图片描述

4.2.3方法重写

4.2.3.1 概念
  • 方法重写 是指子类定义一个与父类中 方法签名完全相同 的方法

  • 重写的方法必须具有相同的 方法名、参数列表 和 返回类型(或者是返回类型的子类型)。

  • 重写的方法用于覆盖父类的实现,提供子类特定的行为

4.2.3.2方法重写的 使用场景 和 重写案例
4.2.3.2.1方法重写场景

发生在子类和父类之间的关系。 子类继承了父类的方法,但子类需要提供与父类不同的实现。 因此,子类重新定义了一个与父类方法 签名完全相同 的方法,以便覆盖父类的该方法。这种机制称为 方法重写(Method Overriding)。

4.2.3.2方法重写案例

在这里插入图片描述
定义一个父类person

java">package Override;public class person {public void eat(){System.out.println("吃米饭,吃菜");}public void drink(){System.out.println("喝开水");}
}

定义一个继承自person的子类Student类
子类继承了父类中的eat(),drink()方法

java">package Override;public class Student extends person {public void lunch(){//就近原则:先在本类中查看eat和drink方法;// 如果有,就调用本类中的方法,如果没有,就调用从父类中继承下来的方法this.eat();this.drink();//super关键字:直接调用父类中的方法super.eat();super.drink();}
}

再定义一个继承自person的子类OverseasStudent类
由于子类认为父类eat()和drink()方法不能满足自己的需求
于是就在子类中重写 继承自父类的eat()和drink()方法

java">package Override;public class OverseasStudent extends person {public void lunch(){//就近原则:先在本类中查看eat和drink方法;// 如果有,就调用本类中的方法,如果没有,就调用从父类中继承下来的方法this.eat();this.drink();//super关键字:直接调用父类中的方法super.eat();super.drink();}//注意:子类中重写的方法上面需要加上@Override//声明不变,重新实现//方法名称与父类全部一样,只是方法体中的功能重写写了!@Overridepublic void eat(){System.out.println("吃意大利面");}@Overridepublic void drink(){System.out.println("喝凉水");}
}

测试类中分别调用OverseasStudent对象的lunch方法 和 Student对象的lunch方法

java">package Override;public class test {public static void main(String[] args){OverseasStudent r1=new OverseasStudent();System.out.println("OverseasStudent类中的lunch方法调用:");r1.lunch();Student r2=new Student();System.out.println("Student类中的lunch方法调用:");r2.lunch();}
}

在这里插入图片描述

4.2.3.3 @Override重写注解

在这里插入图片描述

加上后的子类代码形式如下:

java">package Override;public class OverseasStudent extends person {public void lunch(){//就近原则:先在本类中查看eat和drink方法;// 如果有,就调用本类中的方法,如果没有,就调用从父类中继承下来的方法this.eat();this.drink();//super关键字:直接调用父类中的方法super.eat();super.drink();}//注意:子类中重写的方法上面需要加上@Override//声明不变,重新实现//方法名称与父类全部一样,只是方法体中的功能重写写了!@Overridepublic void eat(){System.out.println("吃意大利面");}@Overridepublic void drink(){System.out.println("喝凉水");}
}
4.2.3.4方法重写的本质

首先:方法重写一定建立在子父类的继承关系之上

方法重写 的本质是:

  • 子类重新定义了父类的方法,提供了自己的实现。

  • 在虚方法表中,子类的方法地址会 覆盖 父类的方法地址。

  • 当通过父类引用调用方法时,JVM 会根据对象的实际类型查找虚方法表,执行子类的方法。

在这里插入图片描述
实例1:
这里的B类继承C类(但是B类中的method2方法进行了重写,所以对应的虚方法表中的method2()方法产生覆盖)
同理:A类继承B类(但是A类中的method2方法也进行了重写,所以对应的虚方法表中method2()方法产生覆盖)
在这里插入图片描述
如图在调用A类对象的成员方法时,要在A类的虚方法表中进行查找
在这里插入图片描述
同理在调用B类对象的成员方法时,要在B类的虚方法表中进行查找

4.2.3.5方法重写的注意事项

在这里插入图片描述
第三条详见:https://blog.csdn.net/2401_82676816/article/details/146328533
注意第五条: 虚方法是指非static方法,非final方法,非private方法

4.2.4方法重写的继承实例----综合练习

在这里插入图片描述

  • 继承体系构建如图:
    在这里插入图片描述
  • 代码如下:
    在这里插入图片描述
java">package Override1;public class Dog {public void eat(){System.out.println("狗吃狗粮");}public void drink(){System.out.println("狗喝水");}public void LookHone(){System.out.println("狗在看家");}
}
java">package Override1;public class Husky extends Dog{//本类中继承了父类的eat(),drink(),LookHome()方法//有一个额外的方法,父类中没有与之同名的方法,故不需要重写public void breakHome(){System.out.println("哈士奇又在拆家了");}
}
java">package Override1;public class ChineseDog extends Dog{//本类中继承了父类的eat()drink(),LookHome()方法//由于中华田园犬吃 剩饭//父类的方法不能满足我们的需求,故eat()方法要进行方法重写//同时,它完全用不到父类中的eat()方法,所以不需要进行super关键字的调用@Overridepublic void eat(){System.out.println("中华田园犬吃剩饭");}
}
java">package Override1;public class SharPei extends Dog{//本类中继承了父类的eat(),drink(),LookHome()方法//由于沙皮狗吃 狗粮和骨头//父类的方法不能满足我们的需求,故eat()方法要进行方法重写@Overridepublic void eat(){super.eat();//这里实际上是在父类eat()方法的基础上,添加了一些额外的行为//所以可以是直接调用父类中的方法,再进行方法的补充System.out.println("狗啃骨头");}
}
java">package Override1;public class test {public static void main(String[] args) {//创建对象并调用Husky h=new Husky();h.eat();//继承自父类h.drink();//继承自父类h.LookHone();//继承自父类h.breakHome();//本类新方法System.out.println("-----------------");ChineseDog c=new ChineseDog();c.eat();//继承自父类c.drink();//继承自父类c.LookHone();//重写的eat()方法}
}

在这里插入图片描述

5. 继承中的构造方法

5.1引入

面向对象编程中,构造方法用于初始化对象的成员变量构造方法的名称必须与类名一致,因此子类无法直接继承父类的构造方法。

然而,子类在初始化过程中需要先完成父类的初始化,才能确保父类的成员变量可以被正确使用。 为此,子类的构造方法中默认会调用super(),即父类的无参构造方法,以确保父类的成员变量先被初始化。

继承后子类构造方法的特点子类的所有构造方法在第一行都会默认调用父类的无参构造方法。 这意味着,子类的初始化过程总是从父类的初始化开始,确保父类的成员变量先被正确初始化,然后再进行子类自身的初始化操作。这种机制确保了对象初始化的逻辑顺序,即“先有父类,再有子类”。

5.2子类中构造方法小结

5.2.1 子父类间构造方法的关系:

在这里插入图片描述

5.2.2 如何调用父类的构造方法:

在这里插入图片描述

5.2.2.1 自动调用父类的无参构造

代码示例:
在这里插入图片描述

java">package GoZao;public class person {String name;int age;//无参构造方法public person() {// 输出测试语句,验证父类无参构造方法的调用System.out.println("父类的无参构造!");}}
java">package GoZao;public class student extends person {// 无参构造方法public student() {// 隐式调用父类的无参构造方法 super();// 这是系统默认的行为,确保父类的成员变量先被初始化super();// 输出测试语句,验证子类无参构造方法的调用System.out.println("子类的无参构造!");}
}
java">package GoZao;public class test {public static void main(String[] args) {//创建子类student的对象//(默认使用无参构造)student s=new student();}
}

在这里插入图片描述

5.2.2.2 手动调用父类的带参构造

代码示例:

java">package GoZao;public class person {String name;int age;//有参构造方法public person(String name, int age) {this.name = name;this.age = age;}
}
java">package GoZao;public class student extends person {// 有参构造方法public student(String name, int age) {// 调用父类的有参构造方法 super(name, age),初始化父类的成员变量super(name, age);// 父类的成员变量初始化完成后,子类可以继续执行其他初始化操作}
}
java">package GoZao;public class test {public static void main(String[] args) {student s=new student("张三",25);System.out.print(s.name+"\t");System.out.println(s.age);}
}

调用分析:

如图:
首先将“张三“传递给子类带参构造的name,25赋值给子类带参构造的age
然后在带参构造中,将name和age(即张三,25)传递给父类的带参构造
然后给对象中的name和age进行赋值

在这里插入图片描述

在这里插入图片描述

5.3 super(…)和this(…)小结

5.3.1super和this的用法格式

在这里插入图片描述

5.3.2super(…)用法演示

代码如下:
在这里插入图片描述

java">package com.example1;// 父类 Personpublic class Person {private String name = "凤姐";private int age = 20;// 父类无参构造方法public Person() {System.out.println("父类无参");}// 父类有参构造方法public Person(String name, int age) {this.name = name;this.age = age;}// Getter 和 Setter 方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
java">package com.example1;// 子类 Student
class Student extends Person {private double score = 100;// 子类无参构造方法public Student() {// 调用父类无参构造方法,默认存在,可以不写,但必须在第一行// super();System.out.println("子类无参");}// 子类有参构造方法public Student(String name, int age, double score) {// 调用父类有参构造方法 Person(String name, int age) 初始化 name 和 agesuper(name, age);this.score = score;System.out.println("子类有参");}// Getter 和 Setter 方法public double getScore() {return score;}public void setScore(double score) {this.score = score;}
}
java">package com.example1;// 测试类
public class test {public static void main(String[] args) {// 调用子类有参构造方法Student s2 = new Student("张三", 20, 99);System.out.println(s2.getScore()); // 输出 99.0System.out.println(s2.getName());  // 输出 张三System.out.println(s2.getAge());  // 输出 20}
}

在这里插入图片描述

5.3.3 super(…)案例图解

父类空间优先于子类对象产生

  1. 初始化顺序
    每次创建子类对象时,先初始化父类的空间,再创建子类对象本身。这是因为子类对象中包含了其对应的父类空间,子类可以继承并使用父类的成员(前提是父类成员没有被 private 修饰)。

  2. 父类成员的使用

    • 如果父类的成员是非 private 修饰的(如 protectedpublic),子类可以直接访问和使用这些成员。
    • 如果父类的成员是 private 修饰的,子类无法直接访问,必须通过父类提供的公共方法(如 gettersetter)来间接访问。
  3. 构造方法的调用顺序

    • 子类的构造方法中会默认调用父类的无参构造方法 super(),以确保父类的成员变量先被初始化。
    • 如果父类没有无参构造方法,或者需要调用父类的有参构造方法,子类必须显式调用 super(参数),并确保 super(参数) 位于子类构造方法的第一行。
  4. 图解理解

    • 父类空间初始化:在子类对象创建时,首先为父类的成员变量分配内存并初始化。
    • 子类对象创建:在父类空间初始化完成后,再为子类的成员变量分配内存并初始化。
    • 成员访问:子类对象中包含父类的成员,因此可以访问和使用父类的非 private 成员。

在这里插入图片描述

5.3.4 this(…)用法演示

this(…)

  • 默认是去找本类中的其他构造方法根据参数来确定具体调用哪一个构造方法
  • 为了借用其他构造方法的功能。

使用场景:调用无参构造时为成员变量赋特定的初值

代码如下:
在这里插入图片描述

java">package GouZaoThis;public class Student {private String name; // 姓名private int age;     // 年龄private char sex;    // 性别/*** 无参构造方法* 通过 this(...) 调用本类的有参构造方法,完成属性初始化*/public Student() {// 调用本类的有参构造方法 Student(String name, int age, char sex)this("徐干", 21, '男');}/*** 有参构造方法* @param name 姓名* @param age  年龄* @param sex  性别*/public Student(String name, int age, char sex) {this.name = name;this.age = age;this.sex = sex;}// Getter 和 Setter 方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public char getSex() {return sex;}public void setSex(char sex) {this.sex = sex;}
}
java">package GouZaoThis;/*** this(...):*    默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。*    为了借用其他构造方法的功能。*/
public class test {public static void main(String[] args) {// 创建 Student 对象,调用无参构造方法Student xuGan = new Student();// 输出对象属性System.out.println(xuGan.getName()); // 输出: 徐干System.out.println(xuGan.getAge());  // 输出: 21System.out.println(xuGan.getSex());   // 输出: 男}
}

调用分析:

如图相当于给成员变量一个默认值
在这里插入图片描述

在这里插入图片描述

5.3.5总结:

面向对象编程中,子类的构造方法具有以下特点:

  1. 默认调用父类无参构造方法
    子类的每个构造方法中都会默认包含一个 super(),用于调用父类的无参构造方法。这是隐式行为,确保父类的成员变量先被初始化。

  2. 手动调用父类构造方法
    如果子类显式调用父类的构造方法(如 super(参数)),则会覆盖默认的 super()。通过 super(参数),可以根据参数的类型和数量调用父类中对应的构造方法。

  3. super()this() 的使用限制

    • super() 用于调用父类的构造方法,this() 用于调用当前类的其他构造方法。
    • 两者都必须位于构造方法的第一行,因此不能同时出现在同一个构造方法中。
  4. super(参数) 的作用
    super(参数) 是根据传入的参数类型和数量,确定调用父类中哪个构造方法。这种方式提供了更灵活的初始化逻辑,允许子类在初始化时选择父类的特定构造方法。

6.写一个继承结构下的标准Javabean类

明天更新

http://www.ppmy.cn/news/1580277.html

相关文章

llamafactory的参数详解 1:(量化等级和方法 RoPE插值方法 加速方式),会对照图片解释,适合小白

前言: 因为刚刚接触大模型,是新手小白,所以刚好学习一下参数是什么意思,在这里分享。 量化等级与量化方法 1. 量化等级(Quantization Levels) 定义: 量化等级是指将连续或高精度的数据&#x…

Android Zygote的进程机制

目录 ✅ Android Zygote 进程机制详解 🚩 一、Zygote 的作用 ⚙️ 二、Zygote 启动流程 ✅ 1. init 进程启动 Zygote ✅ 2. Zygote 初始化虚拟机与核心类库 ✅ 3. Zygote 监听 Socket ✅ 4. Zygote fork 创建应用进程 🔥 三、Zygote 与应用进程之…

Python爬虫:从人民网提取视频链接的完整指南

无论是用于数据分析、内容提取还是资源收集,Python爬虫都因其高效性和易用性而备受开发者青睐。本文将通过一个实际案例——从人民网提取视频链接,详细介绍如何使用Python构建一个完整的爬虫程序。我们将涵盖从基础的网络请求到HTML解析,再到…

【模拟】从 0 到 1:模拟算法的深度剖析与实战指南

文章目录 前言例题一、替换所有的问号二、提莫攻击三、 N 字形变换四、外观数列五、数青蛙 结语 前言 什么是模拟算法? 模拟算法是一种基本的算法思想,可用于考查程序员的基本编程能力,其解决方法就是根据题目给出的规则对题目要求的相关过程…

嵌入式硬件篇---PWM输出通道定时器

文章目录 前言一、PWM通道与定时器的关系1.简介2.定时器作为PWM的时基发生器3.通道作为PWM的输出接口4.协同工作流程5.关键公式 二、输出PWM的设置步骤(通用流程)1. 选择定时器与通道2. 配置时钟源3. 初始化定时器参数预分频器自动重装载 4. 配置PWM模式…

使用OBS进行webRTC推流参考

参考腾讯云官方文档: 云直播 OBS WebRTC 推流_腾讯云 说明非常详细,分为通过WHIP和OBS插件的形式进行推流。 注意:通过OBS插件的形式进行推流需要使用较低的版本,文档里有说明,需要仔细阅读。

centos 7误删/bash 拯救方法

进入救援模式 1. 插入CentOS 7安装光盘,重启系统。在开机时按BIOS设置对应的按键(通常是F2等),将启动顺序调整为CD - ROM优先。 2. 系统从光盘启动后,选择“Troubleshooting”,然后选择“Rescue a Cent…

蓝桥杯 修剪灌木

问题描述 爱丽丝要完成一项修剪灌木的工作。 有 N 棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晩会修剪一棵灌 木, 让灌木的高度变为 0 厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始, 每天向右修剪一棵灌木。当修剪了最右侧的灌木后, 她会调转方向, 下一天开 始向左修…