参考资料:黑马程序员 

1.1 概述

1.1.1 引入

在面向对象编程中,继承是一种重要的机制,它允许我们通过已有类创建新类,实现代码的复用和扩展。让我们通过一个具体案例来理解继承的必要性。

假设我们需要定义以下三个类:

  1. 学生类
  • 属性:姓名、年龄
  • 行为:吃饭、睡觉
  1. 老师类
  • 属性:姓名、年龄、薪水
  • 行为:吃饭、睡觉、教书
  1. 班主任类
  • 属性:姓名、年龄、薪水
  • 行为:吃饭、睡觉、管理

观察这三个类,我们可以发现它们都包含相同的属性和行为(姓名、年龄、吃饭、睡觉),这导致了代码重复。继承机制可以很好地解决这个问题。

【Java】继承_intellij-idea

1.1.2 继承的含义

继承描述的是事物之间的"is-a"关系,即子类是父类的一种特殊类型。例如:

  • 学生是人(Student is a Human)
  • 老师是人(Teacher is a Human)
  • 班主任是人(HeadTeacher is a Human)

通过继承,我们可以:

  1. 将共性的属性和行为放在父类中
  2. 让子类继承这些共性
  3. 在子类中添加特有的属性和行为

1.1.3 继承的好处

  1. 代码复用:避免重复编写相同的代码
  2. 扩展性:可以在不修改父类的情况下扩展功能
  3. 维护性:修改父类可以影响所有子类,便于维护
  4. 多态基础:为多态的实现提供了可能

1.2 继承的格式

在Java中,使用extends关键字实现继承:

class ParentClass {
    // 父类成员
}

class ChildClass extends ParentClass {
    // 子类特有成员
}

注意

  • Java是单继承语言,一个类只能直接继承一个父类;虽然不支持多继承,但是支持多层继承
  • Java中所有的类都直接或者间接继承于object类
  • 子类可以继承父类的非私有成员(属性和方法)
  • 子类可以添加自己的特有成员

1.3 继承案例实现

1.3.1 类设计

根据前面的分析,我们可以设计如下类结构:

  1. Human类(父类)
  • 属性:name, age
  • 行为:eat(), sleep()
  1. Student类(子类)
  • 继承Human的所有成员
  1. Teacher类(子类)
  • 继承Human的所有成员
  • 新增属性:salary
  • 新增行为:teach()
  1. HeadTeacher类(子类)
  • 继承Human的所有成员
  • 新增属性:salary
  • 新增行为:manage()

1.3.2 代码实现

1. 父类Human

public class Human {
    private String name;
    private int 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;
    }

    // 通用行为
    public void eat() {
        System.out.println(name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
}

2. 子类Teacher

public class Teacher extends Human {
    private double salary;

    public void teach() {
        System.out.println(getName() + "老师正在授课");
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

3. 子类HeadTeacher

public class HeadTeacher extends Human {
    private double salary;

    public void manage() {
        System.out.println(getName() + "班主任正在管理班级");
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

4. 子类Student

public class Student extends Human {
    // 可以直接使用父类的所有非私有成员
    // 也可以添加特有的属性和方法
}

5. 测试类

public class Test {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        teacher.setName("张老师");
        teacher.setAge(35);
        teacher.setSalary(8000);
        teacher.eat();
        teacher.teach();

        HeadTeacher headTeacher = new HeadTeacher();
        headTeacher.setName("李主任");
        headTeacher.setAge(40);
        headTeacher.setSalary(10000);
        headTeacher.sleep();
        headTeacher.manage();

        Student student = new Student();
        student.setName("王同学");
        student.setAge(18);
        student.eat();
        // student.teach(); // 错误!Student类没有teach方法
    }
}

1.4 继承的细节

1.4.1 子类继承的内容

  1. 构造方法:子类不能继承父类的构造方法
  2. 私有成员:子类可以继承父类的私有成员,但不能直接访问
  3. 成员方法:虚方法(所有非private、非static、非final的实例方法默认都是虚方法)可以被继承

1.4.2 访问父类私有成员

虽然子类不能直接访问父类的私有成员,但可以通过父类提供的公共方法间接访问:

public class Parent {
    private int privateField;
    
    public int getPrivateField() {
        return privateField;
    }
    
    public void setPrivateField(int value) {
        this.privateField = value;
    }
}

public class Child extends Parent {
    public void accessParentField() {
        // System.out.println(privateField); // 错误!不能直接访问
        System.out.println(getPrivateField()); // 正确!通过公共方法访问
    }
}

1.5 成员变量继承

1.5.1 成员变量不重名

当子类和父类的成员变量不重名时,访问没有影响:

class Parent {
    int parentField = 10;
}

class Child extends Parent {
    int childField = 20;
    
    void show() {
        System.out.println("父类字段: " + parentField);
        System.out.println("子类字段: " + childField);
    }
}

3.5.2 成员变量重名

当子类和父类的成员变量重名时,遵循"就近原则":

class Parent {
    int field = 10;
}

class Child extends Parent {
    int field = 20;
    
    void show() {
        System.out.println("子类字段: " + field); // 输出20
        System.out.println("父类字段: " + super.field); // 输出10
    }
}

1.5.3 super关键字

super关键字用于显式访问父类的成员:

  1. 访问父类的成员变量:super.variableName
  2. 调用父类的方法:super.methodName()
  3. 调用父类的构造方法:super()

示例

class Parent {
    String name = "Parent";
    
    void show() {
        System.out.println("Parent show");
    }
}

class Child extends Parent {
    String name = "Child";
    
    void show() {
        System.out.println("Child show");
        super.show(); // 调用父类方法
        System.out.println("Parent name: " + super.name); // 访问父类变量
    }
}

1.6成员方法继承

1.6.1 成员方法不重名的情况

当子类和父类中的成员方法不重名时,方法调用遵循以下规则:

  • 子类对象调用方法时,首先在子类中查找对应方法
  • 如果子类中不存在该方法,则向上到父类中查找
  • 这种查找过程会沿着继承链一直向上,直到找到对应方法或抛出异常
class Parent {
    public void parentMethod() {
        System.out.println("父类方法执行");
    }
}

class Child extends Parent {
    public void childMethod() {
        System.out.println("子类特有方法执行");
    }
}

public class Test {
    public static void main(String[] args) {
        Child child = new Child();
        child.parentMethod();  // 调用继承自父类的方法
        child.childMethod();   // 调用子类特有方法
    }
}

1.6.2 成员方法重名的情况(方法重写)

当子类和父类中出现重名方法时,子类方法会覆盖父类方法,这是面向对象中多态性的重要体现。

class Parent {
    public void commonMethod() {
        System.out.println("父类实现");
    }
}

class Child extends Parent {
    @Override
    public void commonMethod() {
        System.out.println("子类重写实现");
    }
}

public class Test {
    public static void main(String[] args) {
        Child child = new Child();
        child.commonMethod();  // 输出"子类重写实现"
    }
}

1.7 方法重写详解

1.7.1 方法重写的核心概念

方法重写(Override)是指子类重新定义父类中已有的方法,需要满足以下条件:

  1. 方法名、参数列表必须完全相同
  2. 返回类型可以是父类方法返回类型的子类(协变返回类型)
  3. 访问修饰符不能比父类更严格
  4. 不能重写被final修饰的方法

1.7.2 @Override注解的重要性

使用@Override注解可以带来以下好处:

  • 编译器会检查是否确实重写了父类方法
  • 提高代码可读性,明确表明这是重写的方法
  • 防止因拼写错误导致意外创建新方法
class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

class Cat extends Animal {
    @Override  // 明确表示这是重写方法
    public void makeSound() {
        System.out.println("喵喵叫");
    }
}

1.7.3 方法重写的注意事项

  1. 访问权限:子类重写方法的访问权限不能低于父类方法
  • 父类方法为public,子类必须为public
  • 父类方法为protected,子类可为protected或public
  1. 异常处理
  • 子类方法抛出的异常不能比父类方法更宽泛
  • 可以选择不抛出异常或抛出更具体的异常
  1. 静态方法:静态方法不能被重写,只能被隐藏

1.8 继承中的构造方法特性

1.8.1 构造方法的继承规则

  1. 构造方法不可继承:子类不能继承父类的构造方法
  2. 隐式调用:子类构造方法首行会隐式调用父类无参构造方法(super())
  3. 显式调用:如果父类没有无参构造,必须显式调用父类有参构造

1.8.2 构造方法执行顺序示例

class Person {
    private String name;
    
    public Person(String name) {
        this.name = name;
        System.out.println("Person构造方法执行");
    }
}

class Student extends Person {
    private int grade;
    
    public Student(String name, int grade) {
        super(name);  // 必须显式调用父类有参构造
        this.grade = grade;
        System.out.println("Student构造方法执行");
    }
}

public class Main {
    public static void main(String[] args) {
        Student s = new Student("张三", 90);
        /* 输出:
           Person构造方法执行
           Student构造方法执行
        */
    }
}

1.8.3 构造方法调用要点

  1. super()必须第一行:在子类构造方法中,super()调用必须是第一条语句
  2. 默认无参调用:如果没有显式调用super(),编译器会自动添加对父类无参构造的调用
  3. 构造方法链:对象构造会从最顶层的父类开始,依次向下执行