Java面向对象(进阶)-- super关键字的使用与子类对象实例化全过程

一、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();

    }
}

输出结果:
image.png


举例2

如何在子类方法(Student.java里面,还能够调用父类中被重写的方法呢?

如果此时在子类方法里面调用eat()方法,毫无疑问,这个eat()方法指的是自己类里面重写的方法。如下:

image.png

当然,使用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();
    }
}

运行结果:

image.png


举例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,还没有找到就会报错了。

画个图看看:

image.png

此时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();
    }
}

运行结果:

image.png

小结
  • 方法前面没有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一下:

image.png

所以,属性没有方法那样有覆盖之说

属性不会覆盖,而方法可以覆盖。


举例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方法一样,比如:

image.png

再比如:

image.png

当时解决办法是这样:

image.png

具体关于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();
    }
}

运行结果:

image.png

举例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();
    }
}

输出结果:

image.png


加super与this,就是区分重名的属性,重写的方法。

若没有重名属性和重写的方法,this与super加不加无所谓,只不过一个本类找,一个直接父类找,过程上有区别,结果上没有区别。

若是父类中没有的属性(本类中有),用super直接父类中找是会报错的。

image.png

小结

总结:起点不同(就近原则)

  • 变量前面没有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属性。

image.png

这里我们知道内存中有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){

    }
}

比如在子类中调用父类中的空参构造器:

image.png

举例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();
    }
}

输出结果:

image.png

当我们调用子类构造器创建对象的时候,先调用父类构造器,输出Person()...,然后再输出Student()...。如下:

image.png

可以在子类构造器中调用父类的构造器,格式就是super(形参列表)。

举例3

② 规定:“super(形参列表)”,必须声明在构造器的首行。(和this很像)

如下:

image.png


③ 我们前面讲过,在构造器的首行可以使用"this(形参列表)",调用本类中重载的构造器, 结合②,结论:在构造器的首行,“this(形参列表)” 和 "super(形参列表)"只能二选一。

比如:

image.png


④ 如果在子类构造器的首行既没有显示调用"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);
    }
}

输出结果:

image.png


Student(String name,int age)构造器首行,并没有写this(形参列表),也没有写"super(形参列表)"。此时会默认是Super(),而且是空参的。

来Debug看一下:

image.png

进入构造器了,如下:

image.png

再下一步:

image.png

所以,在调Student构造器的时候,没有写this,也没有super语句,会默认父类空参构造器。

若此时将空参构造器注释掉,会发现子类中两个构造器都会报错。第一个是显示调用,但没有找到空参构造器,就会报错;第二个虽然没有显示调用谁,但是默认调用了空参构造器,也没有找到,所以也会报错。如下:

image.png


⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。 只能是这两种情况之一。

画个图瞅瞅:

image.png


⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了"this(形参列表)“(若有n个就会形成一个环,递归了),则剩下的那个一定使用"super(形参列表)”(显示或者默认)。

子类中的任何一个构造器,都会直接或间接地调用父类的构造器。如下:

image.png

开发中常见错误:

如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有空参的构造器,则编译出错。

调用父类构造器不是为了造对象,造对象需要搭配new,调用父类构造器是为了初始化信息–比如将父类的属性、方法加载到内存中。

image.png


举例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()

image.png

以后声明子类构造器的时候,构造器的首行就可以调用父类指定的结构了。

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类没有无参构造
    }
}

image.png

情景举例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类没有无参构造
    }
}

image.png

情景举例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();
    }
}

👻输出结果

image.png

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());
    }
}

👻输出结果

image.png

3、练习3

🌋题目描述

①写一个名为Account的类模拟账户。该类的属性和方法如下图所示。

image.png

该类包括的属性:账号id,余额balance,年利率annualInterestRate;

包含的方法:访问器方法(getter和setter方法),返回月利率的方法getMonthlyInterest(),取款方法withdraw(),存款方法deposit()。

写一个用户程序测试Account类。在用户程序中,创建一个账号为1122、余额为20000、年利率4.5%的Account对象。

使用withdraw方法提款30000元,并打印余额。

再使用withdraw方法提款2500元,使用deposit方法存款3000元,然后打印余额和月利率。

提示:在提款方法withdraw中,需要判断用户余额是否能够满足提款数额的要求,如果不能,应给出提示。

运行结果如图所示。

image.png

②创建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。

运行结果如下图所示。

image.png


🤺代码①

【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());
    }
}

👻运行结果①

image.png


🤺代码②

【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());
    }
}

👻输出结果

image.png

⚡注意

【注意一】

当我们写完第一问的时候,第二问写CHeckAccount继承于Account,就会报错。

如下:

image.png

这是为哈呢?

在声明一个类没有显示写构造器的时候,会默认有一个空参的构造器。任何一个构造器的首行,要么写this(形参列表),要么是super(形参列表)。若现在构造器也没有写,那就是默认super()。

而现在父类Account里面根本没有提供空参构造器,所以会报错。

image.png

有两种解决办法。

第一种是给父类提供空参的构造器:

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;
    }
}

第二种可以直接调用有参的构造器:

image.png


【注意二】

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("超过可透支限额");
        }
    }
}

这里很容易弄混:

image.png

【小Tips】

按住Ctrl+Shift+向上方向键:本行与上一行互换

【Super的使用】

①子类重写的方法里面,调用父类被重写的方法:

image.png

image.png

(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");
   }
}

最终输出结果是:

image.png

分析一下,看图:

image.png

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");
    }
}

最终输出结果是:

image.png

分析一下,看图:

image.png

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());
    }
}

最终输出结果是:

image.png

分析一下,看图:


image.png


image.png


image.png


image.png

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;
    }
}

最终输出结果是:

image.png

分析一下,看图:


image.png


image.png

二、子类对象实例化全过程

(1)介绍

调用子类构造器去创建对象的时候,会直接或间接地调用父类地构造器。

整个过程其实可以理解为关于某个类的对象,在创建这个对象的过程当中是什么样的场景。
image.png

Dog dog = new Dog("小花","小红");

image.png

(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 = ...;
    }
}

image.png

  1. 从结果的角度来看:体现为类的继承性

当我们创建子类对象后,子类对象就获取了其父类(所有父类,包括直接父类、间接父类)中声明的所有的属性和方法,在权限允许的情况下,可以直接调用。

  1. 从过程的角度来看:(继承性它在内存层面是怎么保证它是能够调用的)

当我们通过子类的构造器创建对象时,子类的构造器一定会直接或间接地调用到其父类的构造器,而其父类的构造器同样会直接或间接地调用到其父类的构造器,…,直到调用了Object类中的构造器为止。

正因为我们调用过子类所有的父类的构造器,所以我们就会将父类中声明的属性、方法加载到内存中,供子类的对象使用

问题:在创建子类对象的过程中,一定会调用父类中的构造器吗? yes!
先有父类的加载,才有子类的加载

  1. 问题:创建子类的对象时,内存中到底有几个对象?
    就只有一个对象(只new了一次)!即为当前new后面构造器对应的类的对象。

①造对象–>new

②构造器–>初始化<init>


叨叨:
这一篇写累死我了,战线拉得很长,不过值得,有任何错误的地方欢迎指正

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨翼轻尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值