文章目录
- 一、super关键字的使用
- (1)为什么需要super?
- (2)super的理解
- (3)super可以调用的结构
- 1、super调用方法
- 举例1
- 举例2
- 举例3
- 小结
- 2、super调用属性
- 举例1
- 举例2
- 举例3
- 小结
- 3、super调用构造器
- 引入
- 举例1
- 举例2
- 举例3
- 举例4
- 4、总结
- super调用方法、属性
- super调用构造器
- (4)小结:this与super
- 1、this和super的意义
- 2、this和super的使用格式
- (5)练习
- 1、练习1
- 2、练习2
- 3、练习3
- (6)面试题
- 1、第一题
- 2、第二题
- 3、第三题
- 4、第四题
- 二、子类对象实例化全过程
- (1)介绍
- (2)举例
一、super关键字的使用
(1)为什么需要super?
举例1:子类继承父类以后,对父类的方法进行了重写,那么在子类中,是否还可以对父类中被重写的方法进行调用?
可以!
举例2:子类继承父类以后,发现子类和父类中定义了同名的属性(若子类造对象,就会有两个同名属性),是否可以在子类中区分两个同名的属性?(方法可以覆盖,属性不能覆盖)
可以!
如何调用? 使用super
关键字即可。
(2)super的理解
super的理解:父类的
在子类中,若想调用父类中被重写的方法,就用super.方法
即可;若想调用父类中的属性,就用super.属性
即可。
若没有写super,调用的就是子类中重写的方法和子类里面声明的属性。
在Java类中使用super来调用父类中的指定操作:
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造器中调用父类的构造器
注意:
- 尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
(3)super可以调用的结构
super可以调用的结构:属性、方法、构造器
具体的:
1、super调用方法
- 如果子类没有重写父类的方法,只要权限修饰符允许,在子类中完全可以直接调用父类的方法;
- 如果子类重写了父类的方法,在子类中需要通过
super.
才能调用父类被重写的方法,否则默认调用的子类重写的方法
举例1
观察下面代码的输出结果。
【Person.java】
package yuyi01;
public class Person {
//属性
String name;
private int age;
//方法
public void eat(){
System.out.println("人吃饭");
}
public void sleep(){
System.out.println("人睡觉");
}
}
【Student.java】
package yuyi01;
public class Student extends Person {
//属性
String school;
//方法
public void study(){
System.out.println("学生学习");
}
//重写
public void eat(){
System.out.println("学生多吃有营养的食物");
}
public void sleep(){
System.out.println("学生保证每天不低于七小时睡眠");
}
}
【StudentTest.java】
package yuyi01;
public class StudentTest {
public static void main(String[] args) {
Student s1=new Student();
s1.eat();
s1.sleep();
}
}
输出结果:
举例2
如何在子类方法(Student.java里面,还能够调用父类中被重写的方法呢?
如果此时在子类方法里面调用eat()
方法,毫无疑问,这个eat()方法指的是自己类里面重写的方法。如下:
当然,使用eat()
调用和this.eat()
调用效果一样,前者只是省略了this.
而已。
若现在想调用父类中的eat()方法,很简单,只需要在前面写super.
即可。(以不影响封装性为前提)
【Student.java】
package yuyi01;
public class Student extends Person {
//属性
String school;
//重写
public void eat(){
System.out.println("学生多吃有营养的食物");
}
//...
public void show(){
eat(); //省略了this
this.eat();
super.eat(); //父类中的eat()方法
}
}
this.eat();
直接在本类找,找到了,就直接调用本类的重写方法即可。
super.eat();
直接在直接父类中找,找到了,就直接调用父类被重写的方法即可。
eat();
是省略了this.
,所以本质上也是调用本类中的方法,若本类中找不到,才会去父类中找。
测试类【StudentTest.java】
package yuyi01;
public class StudentTest {
public static void main(String[] args) {
Student s1=new Student();
//...
s1.show();
}
}
运行结果:
举例3
父类【Person.java】
package yuyi01;
public class Person {
//...
public void doSport(){
System.out.println("人运动");
}
}
子类【Student.java】
package yuyi01;
public class Student extends Person {
//...
public void show1(){
doSport();
}
}
此时子类中调用的doSport()
毫无疑问是父类中的方法,因为子类中没有重写它。
这时候它的前缀是啥呢?
若在本类中调用方法,前缀都会省略this.
。调用show1()
方法的时候,它会在本类中找doSport()
方法,找不到就会去父类中找。
此时本类中没有doSport()方法,就会去父类中找,找到并调用。若父类中还没有,就会继续往上找,直到Object,还没有找到就会报错了。
画个图看看:
此时Sutdent类里面没有重写doSport(),所以只有一个父类Person中的doSport()而已,只能调用它。
从结果上说,this.doSport()
和super.doSport()
一致;但是从过程上来说,this.doSport()
先从本类开始找,super.doSport()
直接向直接父类中找。
【Student.java】
package yuyi01;
public class Student extends Person {
//...
public void show1(){
doSport();
this.doSport();
super.doSport();
}
}
测试类【StudentTest.java】
package yuyi01;
public class StudentTest {
public static void main(String[] args) {
Student s1=new Student();
//...
s1.show1();
}
}
运行结果:
小结
- 方法前面没有super.和this.
- 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
- 方法前面有this.
- 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
- 方法前面有super.
- 从当前子类的直接父类找,如果没有,继续往上追溯
2、super调用属性
- 如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别
- 如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量
- 如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问
举例1
暂且不考虑权限的事情。此时父类Person和子类Student中有同名的属性id
。
父类【 Person.java】
package yuyi01;
public class Person {
//属性
String name;
private int age;
int id; //身份证号
}
子类【Student.java】
package yuyi01;
public class Student extends Person {
//属性
String school;
int id; //学号
}
若此时创建子类Student的对象,那么它拥有几个属性呢?和父类同名的属性会不会被干掉?
来Debug一下:
所以,属性没有方法那样有覆盖之说。
属性不会覆盖,而方法可以覆盖。
举例2
既然有两个同名的属性,那么该如何区分它们呢?
此时将父类和子类中的属性id都赋值。
父类【 Person.java】
package yuyi01;
public class Person {
//属性
String name;
private int age;
int id=1001; //身份证号
}
在子类中写一个show2()方法,输出id的结果是什么呢?
子类【Student.java】
package yuyi01;
public class Student extends Person {
//属性
String school;
int id=1002; //学号
public void show2(){
System.out.println(id); //?
}
}
此时输出语句并没有报错,这里遵循一个就近原则。
就和之前说的get
方法一样,比如:
再比如:
当时解决办法是这样:
具体关于this的讲解在这一篇博客:https://blog.csdn.net/m0_55746113/article/details/134089173?spm=1001.2014.3001.5502
所以此时的id会就近找一个一样的,然后就找到了本类的id。
若想要父类中的id,就需要加一个super.
。
子类【Student.java】
package yuyi01;
public class Student extends Person {
//属性
String school;
int id=1002; //学号
public void show2(){
System.out.println(id); //1002
System.out.println(this.id); //1002
System.out.println(super.id); //1001
}
}
测试类【StudentTest.java】
package yuyi01;
public class StudentTest {
public static void main(String[] args) {
Student s1=new Student();
//...
System.out.println();
s1.show2();
}
}
运行结果:
举例3
父类【Person.java】
package yuyi01;
public class Person {
//属性
String name;
//...
}
子类【Student.java】
package yuyi01;
/**
* ClassName: Student
* Package: yuyi04
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/29 0029 16:40
*/
public class Student extends Person {
//属性
String school;
int id=1002; //学号
//方法
public void show3(){
System.out.println(name); //这样写就相当于省略了this
System.out.println(this.name);
System.out.println(super.name);
}
}
name
先在当前类里面找,若没有找到,就去父类中找。
从结果上,三个输出值是一样的;但从过程上来说,name
先找本类再找父类,super.name
直接找父类。
测试类【StudentTest.java】
package yuyi01;
public class StudentTest {
public static void main(String[] args) {
Student s1=new Student();
s1.show3();
}
}
输出结果:
加super与this,就是区分重名的属性,重写的方法。
若没有重名属性和重写的方法,this与super加不加无所谓,只不过一个本类找,一个直接父类找,过程上有区别,结果上没有区别。
若是父类中没有的属性(本类中有),用super直接父类中找是会报错的。
小结
总结:起点不同(就近原则)
- 变量前面没有super.和this.
- 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的
局部变量
, - 如果不是局部变量,先从当前执行代码的
本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找
父类声明的成员变量
(权限修饰符允许在子类中访问的)
- 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的
- 变量前面有this.
- 通过this找成员变量时,先从当前执行代码的本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)
- 变量前面super.
- 通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)
- 如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)
特别说明:应该避免子类声明和父类重名的成员变量
3、super调用构造器
引入
为何要调用构造器?
比如在Student类里new了一个Student对象,可以通过s1调用属性、方法。
【Student.java】
package yuyi01;
public class Student extends Person {
//属性
String school;
int id=1002; //学号
}
若此时父类Person里面的name属性没有限制,那么在测试里面可以调用name属性。
这里我们知道内存中有name属性,才直接调用它。
可我们new的是Student,相当于调用的是Student的构造器,会加载Student的结构,那为啥还会加载父类的结构呢(为啥内存中有
name属性)?这里就涉及到super调用构造器的问题。
举例1
① 子类继承父类时,不会继承父类的构造器(构造器只有在同名的类里面才有)。只能通过“super(形参列表)
”的方式调用父类指定的构造器。
父类【Person.java】
package yuyi01;
public class Person {
//属性
String name;
private int age;
int id=1001; //身份证号
//构造器
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
}
子类【Student.java】
package yuyi01;
public class Student extends Person {
//属性
String school;
int id=1002; //学号
//测试super调用父类的构造器
public Student(){
}
public Student(String name,int age){
}
}
比如在子类中调用父类中的空参构造器:
举例2
父类【Person.java】
package yuyi01;
public class Person {
//构造器
public Person() {
System.out.println("Person()...");
}
}
子类【Student.java】
package yuyi01;
public class Student extends Person {
//测试super调用父类的构造器
public Student(){
super(); //调用父类中空参的构造器
System.out.println("Student()...");
}
}
测试类【StudentTest.java】
package yuyi01;
public class StudentTest {
public static void main(String[] args) {
Student s2=new Student();
}
}
输出结果:
当我们调用子类构造器创建对象的时候,先调用父类构造器,输出Person()...
,然后再输出Student()...
。如下:
可以在子类构造器中调用父类的构造器,格式就是super(形参列表)。
举例3
② 规定:“super(形参列表)”,必须声明在构造器的首行。(和this很像)
如下:
③ 我们前面讲过,在构造器的首行可以使用"this(形参列表)",调用本类中重载的构造器, 结合②,结论:在构造器的首行,“this(形参列表)” 和 "super(形参列表)"只能二选一。
比如:
④ 如果在子类构造器的首行既没有显示调用"this(形参列表)“,也没有显式调用"super(形参列表)”, 则子类此构造器默认调用"super()",即调用父类中空参的构造器。
父类【Person.java】
package yuyi01;
public class Person {
//构造器
public Person() {
System.out.println("Person()...");
}
}
子类【Student.java】
package yuyi01;
public class Student extends Person {
public Student(String name,int age){
//没有显示调用父类中空参的构造器
}
}
测试类【StudentTest.java】
package yuyi01;
public class StudentTest {
public static void main(String[] args) {
Student s3=new Student("Tom",13);
}
}
输出结果:
在Student(String name,int age)
构造器首行,并没有写this(形参列表),也没有写"super(形参列表)"。此时会默认是Super()
,而且是空参的。
来Debug看一下:
进入构造器了,如下:
再下一步:
所以,在调Student构造器的时候,没有写this,也没有super语句,会默认父类空参构造器。
若此时将空参构造器注释掉,会发现子类中两个构造器都会报错。第一个是显示调用,但没有找到空参构造器,就会报错;第二个虽然没有显示调用谁,但是默认调用了空参构造器,也没有找到,所以也会报错。如下:
⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。 只能是这两种情况之一。
画个图瞅瞅:
⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了"this(形参列表)
“(若有n个就会形成一个环,递归了),则剩下的那个一定使用"super(形参列表)
”(显示或者默认)。
子类中的任何一个构造器,都会直接或间接地调用父类的构造器。如下:
开发中常见错误:
如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有空参的构造器,则编译出错。
调用父类构造器不是为了造对象,造对象需要搭配new,调用父类构造器是为了初始化信息–比如将父类的属性、方法加载到内存中。
举例4
父类【Person.java】
package yuyi01;
public class Person {
//属性
String name;
private int age;
int id=1001; //身份证号
//方法
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//构造器
public Person() {
System.out.println("Person()...");
}
}
子类【Student.java】
package yuyi01;
public class Student extends Person {
//属性
String school;
int id=1002; //学号
public Student(String name,int age){
//没有显示调用父类中空参的构造器
setAge(age);
super.name=name; //当前类里面没有name属性,这里super也可以写成this
}
}
上面的写法很Low,若父类Person中有这样的构造器:
package yuyi01;
public class Person {
//属性
String name;
private int age;
int id=1001; //身份证号
//构造器
public Person() {
System.out.println("Person()...");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
子类可以这样写:
package yuyi01;
public class Student extends Person {
//属性
String school;
int id=1002; //学号
public Student(String name,int age){
super(name,age);
}
}
那么子类就可以直接调用父类中的构造器,如下:(若是没有写,调用的就是父类中的空参构造器Person()
)
以后声明子类构造器的时候,构造器的首行就可以调用父类指定的结构了。
4、总结
子类继承父类以后(super关键字使用的前提是基于继承),我们就可以在子类的方法或构造器中,调用父类中声明的属性或方法。(满足封装性的前提下)
super调用方法、属性
🗃️如何调用呢?
需要使用"super.
"的结构,表示调用父类的属性或方法。
一般情况下,我们可以考虑省略"super."的结构。
但是,如果出现子类重写了父类的方法或子父类中出现了同名的属性时,则必须使用"super.
"的声明,显式地调用父类被重写的方法或父类中声明的同名的属性。
特别说明:应该避免子类声明和父类重名的成员变量(方法没法避开)
在阿里的开发规范等文档中都做出明确说明:
super调用构造器
① 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。
② 规定:“super(形参列表)”,必须声明在构造器的首行。
③ 我们前面讲过,在构造器的首行可以使用"this(形参列表)",调用本类中重载的构造器,
结合②,结论:在构造器的首行,“this(形参列表)” 和 "super(形参列表)"只能二选一。
④ 如果在子类构造器的首行既没有显示调用"this(形参列表)“,也没有显式调用"super(形参列表)”,
则子类此构造器默认调用"super()",即调用父类中空参的构造器。
⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。
只能是这两种情况之一。
⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了"this(形参列表)“,
则剩下的那个一定使用"super(形参列表)”。
–> 我们在通过子类的构造器创建对象时,一定在调用子类构造器的过程中,直接或间接的调用到父类的构造器。
也正因为调用过父类的构造器,我们才会将父类中声明的属性或方法加载到内存中,供子类对象使用。
情景举例1:
class A{
}
class B extends A{
}
class Test{
public static void main(String[] args){
B b = new B();
//A类和B类都是默认有一个无参构造,B类的默认无参构造中还会默认调用A类的默认无参构造
//但是因为都是默认的,没有打印语句,看不出来
}
}
情景举例2:
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
}
class Test{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类默认有一个无参构造,
//B类的默认无参构造中会默认调用A类的无参构造
//可以看到会输出“A类无参构造器"
}
}
情景举例3:
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
B(){
System.out.println("B类无参构造器");
}
}
class Test{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类显示声明一个无参构造,
//B类的无参构造中虽然没有写super(),但是仍然会默认调用A类的无参构造
//可以看到会输出“A类无参构造器"和"B类无参构造器")
}
}
情景举例4:
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
B(){
super();
System.out.println("B类无参构造器");
}
}
class Test{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类显示声明一个无参构造,
//B类的无参构造中明确写了super(),表示调用A类的无参构造
//可以看到会输出“A类无参构造器"和"B类无参构造器")
}
}
情景举例5:
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
System.out.println("B类无参构造器");
}
}
class Test05{
public static void main(String[] args){
B b = new B();
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个无参构造,
//B类的无参构造没有写super(...),表示默认调用A类的无参构造
//编译报错,因为A类没有无参构造
}
}
情景举例6:
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
super();
System.out.println("B类无参构造器");
}
}
class Test06{
public static void main(String[] args){
B b = new B();
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个无参构造,
//B类的无参构造明确写super(),表示调用A类的无参构造
//编译报错,因为A类没有无参构造
}
}
情景举例7:
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(int a){
super(a);
System.out.println("B类有参构造器");
}
}
class Test07{
public static void main(String[] args){
B b = new B(10);
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个有参构造,
//B类的有参构造明确写super(a),表示调用A类的有参构造
//会打印“A类有参构造器"和"B类有参构造器"
}
}
情景举例8:
class A{
A(){
System.out.println("A类无参构造器");
}
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
super();//可以省略,调用父类的无参构造
System.out.println("B类无参构造器");
}
B(int a){
super(a);//调用父类有参构造
System.out.println("B类有参构造器");
}
}
class Test8{
public static void main(String[] args){
B b1 = new B();
B b2 = new B(10);
}
}
(4)小结:this与super
1、this和super的意义
this:当前对象
- 在构造器和非静态代码块中,表示正在new的对象
- 在实例方法中,表示调用当前方法的对象
super:引用父类声明的成员
2、this和super的使用格式
- this
- this.成员变量:表示当前对象的某个成员变量,而不是局部变量
- this.成员方法:表示当前对象的某个成员方法,完全可以省略this.
- this()或this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错
- super
- super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
- super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
- super()或super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
(5)练习
1、练习1
🌋题目描述
修改方法重写的练习2中定义的类Kids中employeed()方法,在该方法中调用父类ManKind的employeed()方法,
然后再输出"but Kids should study and no job."
【Kids.java】
package yuyi02;
/**
* ClassName: Kids
* Package: yuyi05
* Description:
修改继承内容的练习1中定义的类Kids,在Kids中重新定义employeed()方法,
覆盖父类ManKind中定义的employeed()方法,输出"Kids should study and no job."
* @Author 雨翼轻尘
* @Create 2023/10/30 0030 10:56
*/
public class Kids extends Mankind { //父类中声明的属性和方法都被继承到子类了,构造器就不提了。后边提super关键字的时候会提到,在子类当中调用父类中的构造器
private int yearOld;
public int getYearOld() {
return yearOld;
}
public void setYearOld(int yearOld) {
this.yearOld = yearOld;
}
public void printAge(){
System.out.println("I am "+yearOld+" years old");
}
@Override
public void employeed() {
System.out.println("Kids should study and no job.");
}
//构造器
public Kids(){
}
public Kids(int yearOld){
this.yearOld=yearOld;
}
//把父类中的属性也做一个赋值,包括自己的属性
public Kids(int sex, int salary,int yearOld){
this.yearOld=yearOld;
//sex、salary两个 属性是父类继承过来的,怎么给他们赋值?
setSex(sex);
setSalary(salary);
}
}
【Mankind.java】
package yuyi02;
/**
* ClassName: Mankind
* Package: yuyi05
* Description:
* (1)定义一个ManKind类,包括
* 成员变量int sex和int salary;
* - 方法void manOrWoman():根据sex的值显示“man”(sex==1)或者“woman”(sex==0);
*
* - 方法void employeed():根据salary的值显示“no job”(salary==0)或者“ job”(salary!=0)。
* @Author 雨翼轻尘
* @Create 2023/10/30 0030 10:32
*/
public class Mankind {
//属性
private int sex;
private int salary;
//方法
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public void manOrWoman(){
if(sex==1){
System.out.println("man");
} else if (sex==0) {
System.out.println("woman");
}
}
public void employeed(){
if(salary==0){
System.out.println("no job");
} else {
System.out.println("job");
}
}
//构造器
public Mankind() {
}
public Mankind(int sex, int salary) {
this.sex = sex;
this.salary = salary;
}
}
【KidsTest.java】
package yuyi02;
/**
* ClassName: KidsTest
* Package: yuyi05
* Description:
*(3)定义类KidsTest,在类的main方法中实例化Kids的对象someKid,用该对象访问其父类的成员变量及方法。
* @Author 雨翼轻尘
* @Create 2023/10/30 0030 10:58
*/
public class KidsTest {
public static void main(String[] args) {
Kids someKid=new Kids();
someKid.setSex(1);
someKid.setSalary(100);
someKid.setYearOld(12);
//Kids类自己声明的方法
someKid.printAge();
//来自于父类中声明的方法
someKid.manOrWoman();
someKid.employeed();
//
System.out.println("*************");
someKid.employeed();
}
}
🤺代码
【 Kids.java】
package yuyi02;
/**
* ClassName: Kids
* Package: yuyi05
* Description:
修改方法重写的练习2中定义的类Kids中employeed()方法,在该方法中调用父类ManKind的employeed()方法,
然后再输出"but Kids should study and no job."
* @Author 雨翼轻尘
* @Create 2023/11/4 0030 10:56
*/
public class Kids extends Mankind { //父类中声明的属性和方法都被继承到子类了,构造器就不提了。后边提super关键字的时候会提到,在子类当中调用父类中的构造器
//...
@Override
public void employeed() {
//在子类中调用父类中被重写的方法
super.employeed(); //先去调用父类中的employeed()方法
System.out.println("but Kids should study and no job.");
}
//...
}
【Mankind.java】
package yuyi02;
public class Mankind {
//...
public void employeed(){
if(salary==0){
System.out.println("no job");
} else {
System.out.println("job");
}
}
}
【KidsTest.java】
package yuyi02;
public class KidsTest {
public static void main(String[] args) {
//...
Kids someKid=new Kids();
someKid.employeed();
}
}
👻输出结果
2、练习2
🌋题目描述
在Cylinder类中修改求表面积的方法findArea()和求体积的方法findVolume(),使用上super。
【Circle.java】
package yuyi03;
/**
* ClassName: Circle
* Package: yuyi06
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/31 0031 10:07
*/
public class Circle {
//属性
private double radius; //半径
//方法
public void setRadius(double radius){
this.radius=radius;
}
public double getRadius(){
return radius;
}
//求圆的面积
public double findArea(){
return Math.PI*radius*radius;
}
//构造器
public Circle(){
radius=1;
}
}
【Cylinder.java】
package yuyi03;
/**
* ClassName: Cylinder
* Package: yuyi06
* Description:
* 圆柱类
* @Author 雨翼轻尘
* @Create 2023/10/31 0031 10:19
*/
public class Cylinder extends Circle {
//属性
private double length; //高
//方法
public void setLength(double length){
this.length=length;
}
public double getLength(){
return length;
}
//求圆柱的体积
public double findVolume(){
return Math.PI*getRadius()*getRadius()*getLength(); //底面积*高
//return findArea()*getLength(); //错误的
}
//构造器
public Cylinder(){
length=1;
}
//求表面积
@Override
public double findArea() {
return Math.PI*getRadius()*getRadius()*2+
2*Math.PI*getRadius()*getLength();
}
}
【CylinderTest.java】
package yuyi03;
/**
* ClassName: CylinderTest
* Package: yuyi06
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/10/31 0031 10:29
*/
public class CylinderTest {
public static void main(String[] args) {
Cylinder cy=new Cylinder();
cy.setRadius(2.3);
cy.setLength(1.4);
System.out.println("圆柱的体积为: "+cy.findVolume());
System.out.println("圆柱的表面积为: "+cy.findArea());
}
}
🤺代码
【Cylinder.java】
package yuyi03;
public class Cylinder extends Circle {
//...
//求圆柱的体积
public double findVolume(){
//return Math.PI*getRadius()*getRadius()*getLength(); //底面积*高 正确的
//return findArea()*getLength(); //错误的
return super.findArea()*getLength();
}
//...
//求表面积
@Override
public double findArea() {
return Math.PI*getRadius()*getRadius()*2+
2*Math.PI*getRadius()*getLength();
}
}
【Circle.java】
package yuyi03;
public class Circle {
//...
//求圆的面积
public double findArea(){
return Math.PI*radius*radius;
}
}
【CylinderTest.java】
package yuyi03;
public class CylinderTest {
public static void main(String[] args) {
Cylinder cy=new Cylinder();
cy.setRadius(2.3);
cy.setLength(1.4);
System.out.println("圆柱的体积为: "+cy.findVolume());
System.out.println("圆柱的表面积为: "+cy.findArea());
}
}
👻输出结果
3、练习3
🌋题目描述
①写一个名为Account的类模拟账户。该类的属性和方法如下图所示。
该类包括的属性:账号id,余额balance,年利率annualInterestRate;
包含的方法:访问器方法(getter和setter方法),返回月利率的方法getMonthlyInterest(),取款方法withdraw(),存款方法deposit()。
写一个用户程序测试Account类。在用户程序中,创建一个账号为1122、余额为20000、年利率4.5%的Account对象。
使用withdraw方法提款30000元,并打印余额。
再使用withdraw方法提款2500元,使用deposit方法存款3000元,然后打印余额和月利率。
提示:在提款方法withdraw中,需要判断用户余额是否能够满足提款数额的要求,如果不能,应给出提示。
运行结果如图所示。
②创建Account类的一个子类CheckAccount代表可透支的账户,该账户中定义一个属性overdraft代表可透支限额。
在CheckAccount类中重写withdraw方法,其算法如下:
————————————————————————————————————————
如果(取款金额<账户余额),
可直接取款
如果(取款金额>账户余额),
计算需要透支的额度
判断可透支额overdraft是否足够支付本次透支需要,如果可以
将账户余额修改为0,冲减可透支金额
如果不可以
提示用户超过可透支额的限额
————————————————————————————————————————
要求:写一个用户程序测试CheckAccount类。
在用户程序中,创建一个账号为1122、余额为20000、年利率4.5%,可透支限额为5000元的CheckAccount对象。
使用withdraw方法提款5000元,并打印账户余额和可透支额。
再使用withdraw方法提款18000元,并打印账户余额和可透支额。
再使用withdraw方法提款3000元,并打印账户余额和可透支额。
提示:
(1)子类CheckAccount的构造方法需要将从父类继承的3个属性和子类自己的属性全部初始化。
(2)父类Account的属性balance被设置为private,但在子类CheckAccount的withdraw方法中需要修改它的值,因此应修改父类的balance属性,定义其为protected。
运行结果如下图所示。
🤺代码①
【Account.java】
package yuyi04;
/**
* ClassName: Account
* Package: yuyi04
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/4 0004 8:29
*/
public class Account {
//属性
private int id; //账户
private double balance; //余额
private double annualInterestRate; //年利率
//构造器
public Account(int id,double balance,double annualInterestRate){
//super();
this.id=id;
this.balance=balance;
this.annualInterestRate=annualInterestRate;
}
//方法
public void setId(int id) {
this.id = id;
}
public void setBalance(double balance) {
this.balance = balance;
}
public int getId() {
return id;
}
public double getBalance() {
return balance;
}
public void setAnnualInterestRate(double annualInterestRate) {
this.annualInterestRate = annualInterestRate;
}
/**
* 获取月利率
* @return
*/
public double getMonthlyInterest(){
return annualInterestRate / 12;
}
/**
* 取钱曹操作
* @param amount 要取的钱数
*/
public void withdraw(double amount){
if(balance>=amount){
balance-=amount;
}else{
System.out.println("余额不足!");
}
}
/**
* 存钱操作
* @param amount 要存的额度
*/
public void deposit(double amount){
if(amount>0){
balance+=amount;
}
}
}
【AccountTest.java】
package yuyi04;
/**
* ClassName: AccountTest
* Package: yuyi04
* Description:
* 写一个用户程序测试Account类。在用户程序中,创建一个账号为1122、余额为20000、年利率4.5%的Account对象。
* 使用withdraw方法提款30000元,并打印余额。
* 再使用withdraw方法提款2500元,使用deposit方法存款3000元,然后打印余额和月利率。
* @Author 雨翼轻尘
* @Create 2023/11/4 0004 10:46
*/
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account(1122,20000,0.045);
acct.withdraw(30000);
System.out.println("您的账户余额为:"+acct.getBalance());
acct.withdraw(2500);
acct.deposit(3000);
System.out.println("您的账户余额为: "+acct.getBalance());
System.out.println("月利率为: "+acct.getMonthlyInterest());
}
}
👻运行结果①
🤺代码②
【Account.java】
package yuyi04;
/**
* ClassName: Account
* Package: yuyi04
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/4 0004 8:29
*/
public class Account {
//属性
private int id; //账户
private double balance; //余额
private double annualInterestRate; //年利率
//构造器
public Account(int id,double balance,double annualInterestRate){
//super();
this.id=id;
this.balance=balance;
this.annualInterestRate=annualInterestRate;
}
//方法
public void setId(int id) {
this.id = id;
}
/*public void setBalance(double balance) {
this.balance = balance;
}*/
public int getId() {
return id;
}
public double getBalance() {
return balance;
}
public void setAnnualInterestRate(double annualInterestRate) {
this.annualInterestRate = annualInterestRate;
}
/**
* 获取月利率
* @return
*/
public double getMonthlyInterest(){
return annualInterestRate / 12;
}
/**
* 取钱曹操作
* @param amount 要取的钱数
*/
public void withdraw(double amount){
if(balance>=amount){
balance-=amount;
}else{
System.out.println("余额不足!");
}
}
/**
* 存钱操作
* @param amount 要存的额度
*/
public void deposit(double amount){
if(amount>0){
balance+=amount;
}
}
}
【CheckAccount.java】
package yuyi04;
/**
* ClassName: CheckAccount
* Package: yuyi04
* Description:
* 创建Account类的一个子类CheckAccount代表可透支的账户,该账户中定义一个属性overdraft代表可透支限额。
* @Author 雨翼轻尘
* @Create 2023/11/4 0004 14:51
*/
public class CheckAccount extends Account{
//属性
private double overdraft; //可透支限额
//方法
public double getOverdraft() {
return overdraft;
}
public void setOverdraft(double overdraft) {
this.overdraft = overdraft;
}
//重写withdraw方法
/**
* 针对于可透支的账户的取钱操作
* @param amount 要取的钱数
*/
public void withdraw(double amount){
if(getBalance()>=amount){
//错误的:(左右结果都是一个值,何来赋值一说)
//getBalance()=getBalance()-amount;
//正确的
super.withdraw(amount); //super别去掉了,这里调用的是父类的withdraw方法
} else if (getBalance()+overdraft>=amount) {
overdraft-=amount-getBalance(); //可透支的限额剩余量
super.withdraw(getBalance()); //把原本账户的钱取光
}else{
System.out.println("超过可透支限额");
}
}
//构造器
public CheckAccount(int id,double balance,double annualInterestRate){
super(id,balance,annualInterestRate);
}
public CheckAccount(int id,double balance,double annualInterestRate,double overdraft){
super(id,balance,annualInterestRate);
this.overdraft=overdraft;
}
}
【CheckAccountTest.java】
package yuyi04;
/**
* ClassName: CheckAccountTest
* Package: yuyi04
* Description:
* 要求:写一个用户程序测试CheckAccount类。
* 在用户程序中,创建一个账号为1122、余额为20000、年利率4.5%,可透支限额为5000元的CheckAccount对象。
*
* 使用withdraw方法提款5000元,并打印账户余额和可透支额。
* 再使用withdraw方法提款18000元,并打印账户余额和可透支额。
* 再使用withdraw方法提款3000元,并打印账户余额和可透支额。
*
* @Author 雨翼轻尘
* @Create 2023/11/4 0004 22:05
*/
public class CheckAccountTest {
public static void main(String[] args) {
CheckAccount checkAccount=new CheckAccount(1122,20000,0.045,5000);
checkAccount.withdraw(5000);
System.out.println("您的账户余额为: "+checkAccount.getBalance());
System.out.println("您的可透支额为: "+checkAccount.getOverdraft());
checkAccount.withdraw(18000);
System.out.println("您的账户余额为: "+checkAccount.getBalance());
System.out.println("您的可透支额为: "+checkAccount.getOverdraft());
checkAccount.withdraw(3000);
System.out.println("您的账户余额为: "+checkAccount.getBalance());
System.out.println("您的可透支额为: "+checkAccount.getOverdraft());
}
}
👻输出结果
⚡注意
【注意一】
当我们写完第一问的时候,第二问写CHeckAccount继承于Account,就会报错。
如下:
这是为哈呢?
在声明一个类没有显示写构造器的时候,会默认有一个空参的构造器。任何一个构造器的首行,要么写this(形参列表),要么是super(形参列表)。若现在构造器也没有写,那就是默认super()。
而现在父类Account里面根本没有提供空参构造器,所以会报错。
有两种解决办法。
第一种是给父类提供空参的构造器:
package yuyi04;
public class Account {
//...
//构造器
//空参构造器
public Account(){
}
public Account(int id,double balance,double annualInterestRate){
//super();
this.id=id;
this.balance=balance;
this.annualInterestRate=annualInterestRate;
}
}
第二种可以直接调用有参的构造器:
【注意二】
CheckAccount类里面重写的withdraw
方法。
package yuyi04;
public class CheckAccount extends Account{
//...
//重写withdraw方法
/**
* 针对于可透支的账户的取钱操作
* @param amount 要取的钱数
*/
public void withdraw(double amount){
if(getBalance()>=amount){
//错误的:(左右结果都是一个值,何来赋值一说)
//getBalance()=getBalance()-amount;
//正确的
super.withdraw(amount); //super别去掉了,这里调用的是父类的withdraw方法
} else if (getBalance()+overdraft>=amount) {
overdraft-=amount-getBalance(); //可透支的限额剩余量
super.withdraw(getBalance()); //把原本账户的钱取光
}else{
System.out.println("超过可透支限额");
}
}
}
这里很容易弄混:
【小Tips】
按住Ctrl+Shift+向上方向键
:本行与上一行互换
【Super的使用】
①子类重写的方法里面,调用父类被重写的方法:
②
(6)面试题
1、第一题
如下代码输出结果是多少?
package com.atguigu05._super.interview;
/**
* 判断运行结果
*
* @author 雨翼轻尘
* @create 2023/11/5
*/
public class Interview01 {
public static void main(String[] args) {
new A(new B());
}
}
class A {
public A() {
System.out.println("A");
}
public A(B b) {
this();
System.out.println("AB");
}
}
class B {
public B() {
System.out.println("B");
}
}
最终输出结果是:
分析一下,看图:
2、第二题
如下代码输出结果是多少?
package com.atguigu05._super.interview;
/**
* 判断运行结果
*
* @author 雨翼轻尘
* @create 2023/11/5
*/
public class Interview01 {
public static void main(String[] args) {
new A(new B());
}
}
class A {
public A() {
System.out.println("A");
}
public A(B b) {
this();
System.out.println("AB");
}
}
class B extends A{
public B() {
System.out.println("B");
}
}
最终输出结果是:
分析一下,看图:
3、第三题
如下代码输出结果是多少?
package yuyi05;
/**
* @author 雨翼轻尘
* @create 2023/11/5
*/
public class Interview02{
public static void main(String[] args) {
Father f = new Father();
Son s = new Son();
System.out.println(f.getInfo());//atyuyi
System.out.println(s.getInfo()); //atyuyi
s.test();//atyuyi atyuyi
System.out.println("-----------------");
s.setInfo("轻尘");
System.out.println(f.getInfo());//atyuyi
System.out.println(s.getInfo());//轻尘
s.test(); //轻尘 轻尘
}
}
class Father{
private String info = "atyuyi";
public void setInfo(String info){
this.info = info;
}
public String getInfo(){
return info;
}
}
class Son extends Father{
private String info = "雨翼轻尘";
public void test(){
System.out.println(this.getInfo());
System.out.println(super.getInfo());
}
}
最终输出结果是:
分析一下,看图:
①
②
③
④
4、第四题
如下代码输出结果是多少?
package yuyi05;
/**
* @author 雨翼轻尘
* @create 2023/11/5
*/
public class Interview02{
public static void main(String[] args) {
Father f = new Father();
Son s = new Son();
System.out.println(f.getInfo());//返回最近的info,即本类的"atyuyi"
System.out.println(s.getInfo()); //返回最近的info,即本类的"雨翼轻尘"
s.test();//“雨翼轻尘” atyuyi
System.out.println("-----------------");
s.setInfo("轻尘");
System.out.println(f.getInfo());//atyuyi
System.out.println(s.getInfo());//雨翼轻尘
s.test(); //雨翼轻尘 轻尘
}
}
class Father{
private String info = "atyuyi";
public void setInfo(String info){
this.info = info;
}
public String getInfo(){
return info;
}
}
class Son extends Father{
private String info = "雨翼轻尘";
public void test(){
System.out.println(this.getInfo());
System.out.println(super.getInfo());
}
//重写
public String getInfo(){
return info;
}
}
最终输出结果是:
分析一下,看图:
①
②
二、子类对象实例化全过程
(1)介绍
调用子类构造器去创建对象的时候,会直接或间接地调用父类地构造器。
整个过程其实可以理解为关于某个类的对象,在创建这个对象的过程当中是什么样的场景。
Dog dog = new Dog("小花","小红");
(2)举例
代码举例:
class Creature{ //生物类
//声明属性、方法、构造器
}
class Animal extends Creature{ //动物类
}
class Dog extends Animal{ //狗类
}
class DogTest{
public static void main(String[] args){
//子类对象DOg在实例化的过程当中整个过程是怎样的呢?
Dog dog = new Dog(); //创建DOg的对象中,涉及到父类、父类的父类加载的过程?
//通过dog调用属性、方法,只要是在Animal或Creature里面定义的属性、方法
dog.xxx();
dog.yyy = ...;
}
}
- 从结果的角度来看:体现为类的继承性
当我们创建子类对象后,子类对象就获取了其父类(所有父类,包括直接父类、间接父类)中声明的所有的属性和方法,在权限允许的情况下,可以直接调用。
- 从过程的角度来看:(继承性它在内存层面是怎么保证它是能够调用的)
当我们通过子类的构造器创建对象时,子类的构造器一定会直接或间接地调用到其父类的构造器,而其父类的构造器同样会直接或间接地调用到其父类的构造器,…,直到调用了Object
类中的构造器为止。
正因为我们调用过子类所有的父类的构造器,所以我们就会将父类中声明的属性、方法加载到内存中,供子类的对象使用。
问题:在创建子类对象的过程中,一定会调用父类中的构造器吗? yes!
先有父类的加载,才有子类的加载。
- 问题:创建子类的对象时,内存中到底有几个对象?
就只有一个对象(只new了一次)!即为当前new后面构造器对应的类的对象。
①造对象–>new
②构造器–>初始化<init>
叨叨:
这一篇写累死我了,战线拉得很长,不过值得,有任何错误的地方欢迎指正