1. 继承概述
格式:
1.1 引例
假如我们要定义如下类: 学生类,老师类和工人类,分析如下。
学生类 属性:姓名,年龄 行为:吃饭,睡觉
老师类 属性:姓名,年龄,薪水 行为:吃饭,睡觉,教书
班主任 属性:姓名,年龄,薪水 行为:吃饭,睡觉,管理
如果我们定义了这三个类去开发一个系统,那么这三个类中就存在大量重复的信息(属性:姓名,年龄。行为:吃饭,睡觉)。
这样就导致了相同代码大量重复,代码显得很臃肿和冗余,那么如何解决呢?
假如多个类中存在相同属性和行为时,我们可以将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。
如图所示:
其中,多个类可以称为子类,单独被继承的那一个类称为父类、超类(superclass)或者基类。
1.2 继承的含义
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。
例如,兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承:就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
继承的本质就是类跟类之间的父子关系
1.3使用继承的场景
1.4继承的好处
2.继承的特点
java_45">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类
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案例说明
请使用继承定义以下类:
- 学生类
属性:姓名,年龄
行为:吃饭,睡觉- 老师类
属性:姓名,年龄,薪水
行为:吃饭,睡觉,教书- 班主任
属性:姓名,年龄,薪水
行为:吃饭,睡觉,管理
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 的值}
}
代码解析:
-
成员变量隐藏:
- 子类
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
。
示例代码:
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(…)案例图解
父类空间优先于子类对象产生
-
初始化顺序:
每次创建子类对象时,先初始化父类的空间,再创建子类对象本身。这是因为子类对象中包含了其对应的父类空间,子类可以继承并使用父类的成员(前提是父类成员没有被private
修饰)。 -
父类成员的使用:
- 如果父类的成员是非
private
修饰的(如protected
或public
),子类可以直接访问和使用这些成员。 - 如果父类的成员是
private
修饰的,子类无法直接访问,必须通过父类提供的公共方法(如getter
和setter
)来间接访问。
- 如果父类的成员是非
-
构造方法的调用顺序:
- 子类的构造方法中会默认调用父类的无参构造方法
super()
,以确保父类的成员变量先被初始化。 - 如果父类没有无参构造方法,或者需要调用父类的有参构造方法,子类必须显式调用
super(参数)
,并确保super(参数)
位于子类构造方法的第一行。
- 子类的构造方法中会默认调用父类的无参构造方法
-
图解理解:
- 父类空间初始化:在子类对象创建时,首先为父类的成员变量分配内存并初始化。
- 子类对象创建:在父类空间初始化完成后,再为子类的成员变量分配内存并初始化。
- 成员访问:子类对象中包含父类的成员,因此可以访问和使用父类的非
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总结:
在面向对象编程中,子类的构造方法具有以下特点:
-
默认调用父类无参构造方法:
子类的每个构造方法中都会默认包含一个super()
,用于调用父类的无参构造方法。这是隐式行为,确保父类的成员变量先被初始化。 -
手动调用父类构造方法:
如果子类显式调用父类的构造方法(如super(参数)
),则会覆盖默认的super()
。通过super(参数)
,可以根据参数的类型和数量调用父类中对应的构造方法。 -
super()
和this()
的使用限制:super()
用于调用父类的构造方法,this()
用于调用当前类的其他构造方法。- 两者都必须位于构造方法的第一行,因此不能同时出现在同一个构造方法中。
-
super(参数)
的作用:
super(参数)
是根据传入的参数类型和数量,确定调用父类中哪个构造方法。这种方式提供了更灵活的初始化逻辑,允许子类在初始化时选择父类的特定构造方法。