Skip to content

继承(extends)


基本介绍

为什么需要继承?

当不同的类中有相同的属性或者方法的时候可以避免重复定义,提高代码的阅读性和复用性

关系结构

(1) 父类(基类,超类

(2) 子类(派生类

(3) 子类继承父类的属性和方法,子类又可以当作父类继续继承子类


代码示例

java
// 父类 A
package extend;

public class A {
    String name;
    int age;
    double height;

    // 声明无参构造器
    public A() {

    }

    // 构造器的声明
    public A(String name, int age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    // 设置属性值
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public void printinfo() {
        System.out.println("A类的print函数被调用");
        System.out.print("姓名:" + name + "\n年龄:" + age + "\n身高:" + height);
    }
}

// 子类 B 继承 父类 A
package extend;

public class B extends A {

    // B 继承 A类, 此时 B 拥有 A 的所有属性和方法,不同的地方重新声明即可,
    // 共有的部分:直接调用即可
    @Override
    public void printinfo() {
        System.out.println("B类的print函数被调用");
        System.out.print("姓名:" + name + "\n年龄:" + age + "\n身高:" + height);
    }
}


// 主函数中调用
package extend;

public class test {
    public static void main(String[] args) {
        A a = new A();
        a.setName("jackson");
        a.setAge(18);
        a.setHeight(1.68);
        a.printinfo();

        System.out.println("\n");

        B b = new B();
        b.setName("bob");
        b.setAge(22);
        b.setHeight(1.73);
        b.printinfo();
    }
}


// 输出结果
A类的print函数被调用
姓名:jackson
年龄:18
身高:1.68

B类的print函数被调用
姓名:bob
年龄:22
身高:1.73

代码分析

(1)使用了继承避免了在 B 类中重复定义相同的属性和方法

(2)B 类继承 A 类后, B 类拥有 A 类的所有属性和方法,是共用的

(3)B 类中修改了 print 函数的提示信息,实则上如果没有提示信息,B 类中可以不写任何内容

小结

子类继承父类后,子类拥有父类的所有属性和方法,如果子类有特定的属性或者方法,在子类中声明即可

使用细节

(1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供的公共方法,然后访问

(2) 子类必须调用父类的构造器,完成父类的初始化,之后才会调用子类的构造器完成子类的初始化

(3) 当创建子类对象时,无论调用子类的哪个构造器,默认情况下总会调用父类的无参构造器。如果父类没有提供无参构造器,子类必须在构造器中显式的用 super 指定调用父类的哪个构造器来完成父类的初始化工作,否则编译报错

(4) 如果希望指定去调用父类的某个构造器,则显式的调用:super(参数列表)

(5) super 在使用时,必须放在构造器第一行,super 只能在构造器中使用

(6) super()this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器

(7) java 所有类都是 Object 类的子类,Object 是所有类的父类

(8)调用父类构造器不止于直接父类,如果父类还有父类,则会一直向上追溯,直到 Object 类(顶级父类)

(9)子类最多只能继承一个父类(指直接继承),即 Java 中是单继承机制

注意:不能滥用继承,子类和父类之间必须满足 is - a 的逻辑关系

(10)可以通过间接的方式实现多继承

例如:A 继承了 B,B 继承了 C,就可以间接的实现 A 继承 B 和 C

代码示例

java
// 父类 A
package extend1;

public class A {

    String name;
    private int age;

    public A(){

    }

    public A(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getage(){
        return this.age;
    }
    public void printinfo() {
        System.out.println("A类的print函数被调用");
        System.out.print("姓名:" + name + "\n年龄:" + age);
    }
}

// 子类 B 继承 父类 A
package extend1;

public class B extends A{
    // age 是私有的,这个时候使用 getage方法访问 age
    public void printinfo() {
        System.out.println("B类的print函数被调用");
        System.out.print("姓名:" + name + "\n年龄:" + getage());
    }

}

// 主函数
package extend1;

public class test {
    public static void main(String[] args) {
        A a = new A();
        a.setName("jackson");
        a.setAge(18);
        a.printinfo();

        System.out.println("\n");

        B b = new B();
        b.setName("bob");
        b.setAge(22);
        b.printinfo();
    }
}

// 输出结果
A类的print函数被调用
姓名:jackson
年龄:18

B类的print函数被调用
姓名:bob
年龄:22.0

代码解析

(1)子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供的公共方法去访问

(2)age 是 private 的,在子类 B 中无法访问,通过在父类中写入 getage 方法,通过继承来调用 getage 方法实现对 private age 属性的访问

继承的过程分析

对象创建好之后会建立查找关系

(1)首先看子类是否有该属性

(2)如果子类有这个属性,并且可以访问,则返回信息

(3)如果子类没有这个属性,就看父类有没有这个属性(如父类有该属性,并且可以访问,就返回信息)

(5) 如果父类没有就按(3)的规则,继续找上级父类,直到 Object…

java
public class pra {
    public static void main(String[] args) {
        Son son = new Son(); // 创建 Son 类的对象
        System.out.println("son.name:" + son.name);
        System.out.println("son.age:" + son.age);
        System.out.println("son.hobby:" + son.hobby);
    }
}

class GrandPa {  // 爷爷类
    String name = "大头爷爷";
    String hobby = "钓鱼";
}

class Father extends GrandPa {  // 父亲类继承爷爷类
    String name = "大头爸爸";
    int age = 39;
}

class Son extends Father {  // 儿子类继承父亲类
    String name = "大头儿子";
}

// 输出结果
son.name:大头儿子
son.age:39
son.hobby:钓鱼

代码分析

son 没有 age 和 hobby 这两个属性,但是访问结果输出了,验证了继承中会寻找父类的属性,如果没有就找到上级父类,直到 object

super()

调用默认构造器

创建子类时,子类的构造器默认会先调用父类的无参构造器,之后调用自身的构造器

java
// 父类A
package extend1;

public class A {

    public A(){
        System.out.print("A类的无参构造器被调用\n");
    }
}

// 子类B 继承 父类A
package extend1;

public class B extends A{
    public B(){
        // super默认存在,优先调用父类的构造器,之后才执行子类的构造器
        super(); // 默认调用父类的无参构造器,如果没有,需要指定使用父类的哪个构造器
        System.out.print("子类B的无参构造器被调用\n");
    }

}

// 主类
package extend1;

public class test {
    public static void main(String[] args) {
        B b = new B();
    }
}

// 输出结果
A类的构造器被调用
子类B的无参构造器被调用

代码解析

创建子类 B 对象时,优先调用父类的无参构造器,输出后,再调用子类的构造器,如果父类的无参构造器被覆盖或者没有显示声明需要用 super 指定调用父类的构造器

调用指定构造器

java
// 父类A
package extend1;

public class A {
    String name;
    int age;

    public A(){
        System.out.println("A类的无参构造器被调用");
    }

    public A(String name) {
        this.name = name;
        System.out.println("A类中public A(String name)构造器被调用");
    }

    public A(int age) {
        this.age = age;
        System.out.println("A类中public A(int age)构造器被调用");
    }

    public A(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("A类中带有全部属性初始化的构造器被调用");
    }

    public void printinfo() {
        System.out.println("A类的print函数被调用");
        System.out.print("姓名:" + name + "\n年龄:" + age);
        System.out.println("\n");
    }

}

// 子类B
package extend1;

public class B extends A{
    public B(){

    }
    public B(int age){
        super(age);
        System.out.println("子类B的无参构造器被调用");
    }
    public void printinfo() {
        System.out.println("B类的print函数被调用");
        System.out.print("姓名:" + name + "\n年龄:" + age);
        System.out.println("\n");
    }

}

// 主类
package extend1;

public class test {
    public static void main(String[] args) {
        /*
        共有的属性
        int age;
        String name

        对于 父类A 可以初始化 age/name/age,name
        对于 子类B 可以初始化 age (使用super指定了)
         */

        A a = new A("jackson",18);
        a.printinfo();

        // 测试 子类B 中的无参构造器
        B b = new B();
        b.printinfo();


        // 测试 子类B 中用super调用主类中指定的构造器
        B b1 = new B(18);
        b1.printinfo();
    }
}

// 输出结果
A类中带有全部属性初始化的构造器被调用
A类的print函数被调用
姓名:jackson
年龄:18

A类的无参构造器被调用
B类的print函数被调用
姓名:null
年龄:0

A类中public A(int age)构造器被调用
子类B的无参构造器被调用
B类的print函数被调用
姓名:null
年龄:18

代码解析

(1)子类调用构造器时优先调用父类的无参构造器

(2)在子类的构造器中可以用super 指定调用父类中的哪一个构造器