当前位置: 首页 > news >正文

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

1. 继承概述

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

1.1 引例

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

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

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

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

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

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

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

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

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

1.2 继承的含义

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

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

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

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

1.3使用继承的场景

在这里插入图片描述

1.4继承的好处

在这里插入图片描述

2.继承的特点

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

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

2.2单继承

在这里插入图片描述

2.3不支持多继承

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

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

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

2.4多层继承

在这里插入图片描述

2.5继承体系

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

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

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

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

2.6继承体系设计的技巧

2.6.1画图法:

画图:从下往上画

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

核心:

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

书写代码:

  • 从上往下写

2.6.2实例练习1

在这里插入图片描述

2.6.2.1体系设计

在这里插入图片描述

2.6.2.2代码实现

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

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案例说明

请使用继承定义以下类:

  1. 学生类
    属性:姓名,年龄
    行为:吃饭,睡觉
  2. 老师类
    属性:姓名,年龄,薪水
    行为:吃饭,睡觉,教书
  3. 班主任
    属性:姓名,年龄,薪水
    行为:吃饭,睡觉,管理
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 变量?
  • 通过父类的 publicprotected 方法访问(如 gettersetter)。
  • 子类可以定义自己的变量,即使与父类 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 的值
    }
}
代码解析:
  1. 成员变量隐藏

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

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

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

4.1.2 变量同名时的访问规则

  • 成员变量隐藏

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

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

在这里插入图片描述

示例代码:

在这里插入图片描述

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(…)案例图解

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

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

  2. 父类成员的使用

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

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

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

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

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

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

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

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

相关文章:

  • [动手学习深度学习]26. 网络中的网络 NiN
  • 个人blog系统 前后端分离 前端js后端go
  • 【保姆级教程】Windows系统+ollama+Docker+Anythingllm部署deepseek本地知识库问答大模型,可局域网多用户访问
  • 深度学习框架PyTorch——从入门到精通(5)构建神经网络
  • 华为OD机试 - 最长回文字符串 - 贪心算法(Java 2024 E卷 100分)
  • 算法 之 ST表
  • 基于Android语言实现身份证二要素核验-身份证实名认证API
  • 【k8s】serviceaccount是给pod使用的与外部访问k8s无关
  • 深入理解事务
  • GoLang 反射
  • pppd拨号模块的总结【Linux】
  • 【从零开始学习计算机科学与技术】计算机网络(五)网络层
  • 【npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree】
  • 3.4 二分查找专题:LeetCode 69. x 的平方根
  • TCP、UDP协议的应用、ServerSocket和Socket、DatagramSocket和DatagramPacket
  • 查询修改ORACLE的server、客户端和导出dmp文件 字符集编码
  • SpringBoot-2整合MyBatis以及基本的使用方法
  • 大模型学习-让其他电脑可访问本地ollama的模型并进行流式响应
  • AMD锐龙8845HS+780M核显 虚拟机安装macOS 15 Sequoia 15.0.1 (2024.10)
  • 【ELK】节省存储 之 压缩存储方式调整
  • 外交部:美方应在平等、尊重和互惠的基础上同中方开展对话
  • 海尔·2025青岛马拉松两选手被终身禁赛:违规转让号码、穿戴他人号码
  • 北京朝阳涉住宅组团地块126亿元成交
  • 一场与纪录并行的伦敦马拉松,超40项新世界纪录诞生
  • 吕国范任河南省人民政府副省长
  • 国家发改委答澎湃:将建立和实施育儿补贴制度,深入实施提振消费专项行动