【java面向对象进阶】------继承
1. 继承概述
格式:
1.1 引例
假如我们要定义如下类: 学生类,老师类和工人类,分析如下。
学生类 属性:姓名,年龄 行为:吃饭,睡觉
老师类 属性:姓名,年龄,薪水 行为:吃饭,睡觉,教书
班主任 属性:姓名,年龄,薪水 行为:吃饭,睡觉,管理
如果我们定义了这三个类去开发一个系统,那么这三个类中就存在大量重复的信息(属性:姓名,年龄。行为:吃饭,睡觉)。
这样就导致了相同代码大量重复,代码显得很臃肿和冗余,那么如何解决呢?
假如多个类中存在相同属性和行为时,我们可以将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。
如图所示:
其中,多个类可以称为子类,单独被继承的那一个类称为父类、超类(superclass)或者基类。
1.2 继承的含义
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。
例如,兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承:就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
继承的本质就是类跟类之间的父子关系
1.3使用继承的场景
1.4继承的好处
2.继承的特点
2.1java只支持单继承,多层继承,不支持多继承
- Java 只能单继承:一个类只能继承一个直接父类,不能同时继承多个类。
- Java 不支持多继承:一个类不能继承多个父类,多个父类可能会有相同的方法,导致菱形继承问题(方法冲突),Java 采用接口来替代多继承。
- Java 支持多层继承:即子类可以继承父类,父类又可以继承更高级的父类,形成继承链,最终所有类都会追溯到
Object
类。- Java 中所有的类都直接或间接继承
Object
类:Object
是 Java 继承体系的顶级父类,所有类默认继承Object
,所以它提供的方法(如toString()
、equals()
、hashCode()
等)可以被所有类使用或重写。
2.2单继承
2.3不支持多继承
不支持多继承的原因:菱形继承问题
什么是菱形继承? 当一个子类同时继承两个父类,而这两个父类又继承自同一个祖先类时,就会出现继承路径交叉,形成菱形结构。
菱形继承带来的问题
方法冲突:如果祖先类中有一个方法,两个父类都继承并可能重写了它,子类在调用这个方法时,不知道该继承哪一个父类的实现。
数据冗余:如果祖先类中有成员变量,而两个父类各自拷贝了一份,子类就会持有两份相同的数据,造成数据不一致的风险。
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类
package a00extend;
//动物类
public class Animal {
public void eat(){
System.out.println("吃东西");
}
public void drink(){
System.out.println("喝水");
}
}
2.直接父类cat类
package a00extend;
//猫类
public class cat extends Animal{
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
3.直接父类dog类
package a00extend;
//狗类
public class dog extends Animal{
public void lookHome(){
System.out.println("狗看家");
}
}
4.子类LiHua类
package a00extend;
//狸花猫类
public class LiHua extends cat{
}
5.子类Ragdoll类
package a00extend;
//布偶猫类
public class Ragdoll extends cat{
}
6.子类Husky 类
package a00extend;
public class Husky extends dog{
public void breakHome(){
System.out.println("哈士奇在拆家");
}
}
7.子类Teddy类
package a00extend;
//泰迪类
public class Teddy extends dog{
public void touch() {
System.out.println("泰迪在蹭一蹭");
}
}
8.Test测试类
package a00extend;
public class Test {
public static void main(String[] args) {
//创建对象并调用方法
//创建布偶猫 对象
cat cat1 = new Ragdoll();
cat1.eat();//来自间接父类Animal
cat1.drink();//来自间接父类Animal
cat1.catchMouse();//来自直接父类cat
System.out.println("-----------------------");
//创建哈士奇 对象
Husky dog1 = new Husky();
dog1.eat();//来自间接父类Animal
dog1.drink();//来自间接父类Animal
dog1.lookHome();//来自直接父类dog
dog1.breakHome();//来自本类Husky
}
}
2.6.3实例练习2
2.6.3.1案例说明
请使用继承定义以下类:
- 学生类
属性:姓名,年龄
行为:吃饭,睡觉- 老师类
属性:姓名,年龄,薪水
行为:吃饭,睡觉,教书- 班主任
属性:姓名,年龄,薪水
行为:吃饭,睡觉,管理
2.6.3.2体系设计
老师类,学生类,还有班主任类,实际上都是属于人类的,我们可以定义一个人类,把他们相同的属性和行为都定义在人类中,然后继承人类即可,子类特有的属性和行为就定义在子类中了。
如下图
2.6.3.3代码实现
1.父类Human类
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类
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类
package a000extend;
public class Student extends Human{
}
4.子类BanZhuren类
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.测试类
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 子类不能继承父类的构造方法
原因:
- 命名冲突:如果子类继承了父类的构造方法,构造方法名将与子类类名不一致,违反了构造方法的命名规则。
- 初始化责任:子类需要初始化自己的成员变量,而父类的构造方法无法直接处理子类的成员变量。
代码示例:
父类:
package a01extend;
//构造方法不能被 子类 继承
public class Fu {
String name;
int age;
//无参构造
public Fu() {
}
//带参构造
public Fu(String name, int age) {
this.name = name;
this.age = age;
}
}
子类:
package a01extend;
public class Zi extends Fu{
//如果一个类中没有构造方法,虚拟机会自动添加一个默认的空参构造
}
测试类:
package a01extend;
public class Test {
// 1. 使用空参构造创建子类对象
// 由于子类 Zi 有空参构造方法(无论是否显式定义),可以直接创建对象
Zi zi = new Zi();
// 2. 尝试使用带参构造创建子类对象(代码会报错)
// 原因:子类 Zi 没有定义带参构造方法,且子类不会继承父类的带参构造方法
// 因此,无法通过传递参数的方式创建子类对象
// a01extend.Zi z2 = new a01extend.Zi("小明", 19); // 这行代码会报错
}
3.2 成员变量
子类可以继承父类中的非private变量,并可以直接赋值调用
子类可以继承父类中的private变量,但不可直接访问和调用
1.
private
成员变量的访问限制
private
修饰的成员变量仅在 定义它的类内部 可访问。- 该变量对 子类不可见,子类无法直接访问或继承
private
变量。2.
private
成员变量的继承特性
- 虽然
private
变量不能被子类直接访问,但它仍然是被继承的。- 继承时,父类的
private
变量仍属于子类对象的一部分,但无法直接访问。3. 如何在子类中访问
private
变量?
- 通过父类的
public
或protected
方法访问(如getter
和setter
)。- 子类可以定义自己的变量,即使与父类
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成员变量的访问特点:
直接调用满足就近原则(谁离我近,我就用谁)
引例分析:
public class Fu {
String name = "Fu"; // 父类的成员变量
}
public class Zi extends Fu {
String name = "Zi"; // 子类的成员变量,隐藏了父类的 name
public void zishow() {
String name = "zishow"; // 局部变量,隐藏了子类的 name
System.out.println(name); // 输出局部变量 name 的值
}
}
代码解析:
-
成员变量隐藏:
- 子类
Zi
中定义了与父类Fu
同名的成员变量name
,这会导致父类的name
被隐藏。 - 在子类中直接访问
name
时,访问的是子类的name
。
- 子类
-
局部变量隐藏:
- 在
zishow
方法中,定义了一个局部变量name
,这会隐藏子类的成员变量name
。 - 在
zishow
方法中访问name
时,访问的是局部变量name
。
- 在
-
就近原则:
- 当存在同名变量时,Java 会优先访问 最近作用域 的变量。
- 在
zishow
方法中,name
的访问顺序是:- 局部变量
name
(最近) - 子类成员变量
name
- 父类成员变量
name
- 局部变量
输出结果:
- 调用
zishow
方法时,输出的是局部变量name
的值,即"zishow"
。
4.1.2 变量同名时的访问规则
成员变量隐藏:
- 子类中定义了与父类同名的成员变量时,父类的成员变量会被隐藏。
- 如果需要访问父类的成员变量,可以使用
super
关键字,例如super.name
。局部变量隐藏:
- 局部变量会隐藏同名的成员变量。
- 如果需要访问被隐藏的成员变量,可以使用
this
关键字,例如this.name
。
示例代码:
package a0000extend;
public class Fu {
String name ="Fu";
}
package a0000extend;
public class Zi extends Fu{
String name="Zi";
public void show()
{
String name="ZiShow";
System.out.println(name);//输出ZiShow
System.out.println(this.name);//输出Zi
System.out.println(super.name);//输出Fu
}
}
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 成员方法的访问特点:
直接调用满足就近原则(谁离我近,我就用谁)
引例分析:
package a011extends;
public class test {
public static void main(String[] args) {
Zi z = new Zi();
//子类中没有show方法,但是可以找到父类方法去执行
z.show();
z.show2();
}
}
package a011extends;
public class Zi extends Fu {
public void show2() {
System.out.println("Zi类中的show2方法执行");
}
}
package a011extends;
public class test {
public static void main(String[] args) {
Zi z = new Zi();
//子类中没有show方法,但是可以找到父类方法去执行
z.show();
z.show2();
}
}
4.2.2 成员方法重名
如果子类父类中出现重名的成员方法(注意这里子类方法要进行 方法重写),则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。
引例:
package com.example;
public class Fu {
public void show() {
System.out.println("Fu show");
}
}
package com.example;
public class Zi extends Fu {
// 子类重写了父类的show方法
@Override
public void show() {
System.out.println("Zi show");
}
}
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
package Override;
public class person {
public void eat(){
System.out.println("吃米饭,吃菜");
}
public void drink(){
System.out.println("喝开水");
}
}
定义一个继承自person的子类Student类
子类继承了父类中的eat(),drink()方法
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()方法
package Override;
public class OverseasStudent extends person {
public void lunch(){
//就近原则:先在本类中查看eat和drink方法;
// 如果有,就调用本类中的方法,如果没有,就调用从父类中继承下来的方法
this.eat();
this.drink();
//super关键字:直接调用父类中的方法
super.eat();
super.drink();
}
//注意:子类中重写的方法上面需要加上@Override
//声明不变,重新实现
//方法名称与父类全部一样,只是方法体中的功能重写写了!
@Override
public void eat(){
System.out.println("吃意大利面");
}
@Override
public void drink(){
System.out.println("喝凉水");
}
}
测试类中分别调用OverseasStudent对象的lunch方法 和 Student对象的lunch方法
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重写注解
加上后的子类代码形式如下:
package Override;
public class OverseasStudent extends person {
public void lunch(){
//就近原则:先在本类中查看eat和drink方法;
// 如果有,就调用本类中的方法,如果没有,就调用从父类中继承下来的方法
this.eat();
this.drink();
//super关键字:直接调用父类中的方法
super.eat();
super.drink();
}
//注意:子类中重写的方法上面需要加上@Override
//声明不变,重新实现
//方法名称与父类全部一样,只是方法体中的功能重写写了!
@Override
public void eat(){
System.out.println("吃意大利面");
}
@Override
public 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方法重写的继承实例----综合练习
- 继承体系构建如图:
- 代码如下:
package Override1;
public class Dog {
public void eat(){
System.out.println("狗吃狗粮");
}
public void drink(){
System.out.println("狗喝水");
}
public void LookHone(){
System.out.println("狗在看家");
}
}
package Override1;
public class Husky extends Dog{
//本类中继承了父类的eat(),drink(),LookHome()方法
//有一个额外的方法,父类中没有与之同名的方法,故不需要重写
public void breakHome(){
System.out.println("哈士奇又在拆家了");
}
}
package Override1;
public class ChineseDog extends Dog{
//本类中继承了父类的eat()drink(),LookHome()方法
//由于中华田园犬吃 剩饭
//父类的方法不能满足我们的需求,故eat()方法要进行方法重写
//同时,它完全用不到父类中的eat()方法,所以不需要进行super关键字的调用
@Override
public void eat(){
System.out.println("中华田园犬吃剩饭");
}
}
package Override1;
public class SharPei extends Dog{
//本类中继承了父类的eat(),drink(),LookHome()方法
//由于沙皮狗吃 狗粮和骨头
//父类的方法不能满足我们的需求,故eat()方法要进行方法重写
@Override
public void eat(){
super.eat();
//这里实际上是在父类eat()方法的基础上,添加了一些额外的行为
//所以可以是直接调用父类中的方法,再进行方法的补充
System.out.println("狗啃骨头");
}
}
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 自动调用父类的无参构造
代码示例:
package GoZao;
public class person {
String name;
int age;
//无参构造方法
public person() {
// 输出测试语句,验证父类无参构造方法的调用
System.out.println("父类的无参构造!");
}
}
package GoZao;
public class student extends person {
// 无参构造方法
public student() {
// 隐式调用父类的无参构造方法 super();
// 这是系统默认的行为,确保父类的成员变量先被初始化
super();
// 输出测试语句,验证子类无参构造方法的调用
System.out.println("子类的无参构造!");
}
}
package GoZao;
public class test {
public static void main(String[] args) {
//创建子类student的对象
//(默认使用无参构造)
student s=new student();
}
}
5.2.2.2 手动调用父类的带参构造
代码示例:
package GoZao;
public class person {
String name;
int age;
//有参构造方法
public person(String name, int age) {
this.name = name;
this.age = age;
}
}
package GoZao;
public class student extends person {
// 有参构造方法
public student(String name, int age) {
// 调用父类的有参构造方法 super(name, age),初始化父类的成员变量
super(name, age);
// 父类的成员变量初始化完成后,子类可以继续执行其他初始化操作
}
}
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(…)用法演示
代码如下:
package com.example1;
// 父类 Person
public 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;
}
}
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 和 age
super(name, age);
this.score = score;
System.out.println("子类有参");
}
// Getter 和 Setter 方法
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
package com.example1;
// 测试类
public class test {
public static void main(String[] args) {
// 调用子类有参构造方法
Student s2 = new Student("张三", 20, 99);
System.out.println(s2.getScore()); // 输出 99.0
System.out.println(s2.getName()); // 输出 张三
System.out.println(s2.getAge()); // 输出 20
}
}
5.3.3 super(…)案例图解
父类空间优先于子类对象产生
-
初始化顺序:
每次创建子类对象时,先初始化父类的空间,再创建子类对象本身。这是因为子类对象中包含了其对应的父类空间,子类可以继承并使用父类的成员(前提是父类成员没有被private
修饰)。 -
父类成员的使用:
- 如果父类的成员是非
private
修饰的(如protected
或public
),子类可以直接访问和使用这些成员。 - 如果父类的成员是
private
修饰的,子类无法直接访问,必须通过父类提供的公共方法(如getter
和setter
)来间接访问。
- 如果父类的成员是非
-
构造方法的调用顺序:
- 子类的构造方法中会默认调用父类的无参构造方法
super()
,以确保父类的成员变量先被初始化。 - 如果父类没有无参构造方法,或者需要调用父类的有参构造方法,子类必须显式调用
super(参数)
,并确保super(参数)
位于子类构造方法的第一行。
- 子类的构造方法中会默认调用父类的无参构造方法
-
图解理解:
- 父类空间初始化:在子类对象创建时,首先为父类的成员变量分配内存并初始化。
- 子类对象创建:在父类空间初始化完成后,再为子类的成员变量分配内存并初始化。
- 成员访问:子类对象中包含父类的成员,因此可以访问和使用父类的非
private
成员。
5.3.4 this(…)用法演示
this(…)
- 默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法
- 为了借用其他构造方法的功能。
使用场景:调用无参构造时为成员变量赋特定的初值
代码如下:
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;
}
}
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()); // 输出: 21
System.out.println(xuGan.getSex()); // 输出: 男
}
}
调用分析:
如图相当于无参构造时给成员变量一个默认值
5.3.5总结:
在面向对象编程中,子类的构造方法具有以下特点:
-
默认调用父类无参构造方法:
子类的每个构造方法中都会默认包含一个super()
,用于调用父类的无参构造方法。这是隐式行为,确保父类的成员变量先被初始化。 -
手动调用父类构造方法:
如果子类显式调用父类的构造方法(如super(参数)
),则会覆盖默认的super()
。通过super(参数)
,可以根据参数的类型和数量调用父类中对应的构造方法。 -
super()
和this()
的使用限制:super()
用于调用父类的构造方法,this()
用于调用当前类的其他构造方法。- 两者都必须位于构造方法的第一行,因此不能同时出现在同一个构造方法中。
-
super(参数)
的作用:
super(参数)
是根据传入的参数类型和数量,确定调用父类中哪个构造方法。这种方式提供了更灵活的初始化逻辑,允许子类在初始化时选择父类的特定构造方法。