一、继承的基本概念

继承是一种由已有类创建新类的机制。新类(子类、派生类)能继承已有类(父类、基类)的属性和方法,并且可以添加自己独有的属性和方法。在 Java 中,继承通过 extends 关键字来实现。

二、继承的语法

class 父类 {
}


class 子类 extends 父类 {
}

三、继承的示例

// 父类
class Shape {
    String color;
    void setColor(String color) {
        this.color = color;
    }
    String getColor() {
        return color;
    }
}
// 子类
class Rectangle extends Shape {
    // 不要重复color属性及方法,
    // 只需要定义新增的width和height属性及方法:
    double width;
    double height;
    double calculateArea() {
        return width * height;
    }
}

四、继承的特点

1. 单继承:Java 只支持单继承,不支持多继承,但支持多层继承。 

2. 传递性:若类 B 继承自类 A,类 C 继承自类 B,那么类 C 也会继承类 A 的属性和方法。

3. 默认继承 Object 类:Java 中所有的类都直接或者间接的继承于 Object 类。如果一个类没有明确继承其他类,那么它会默认继承 Object 类。

五、方法重写

方法重写是指子类重新定义父类中已有的方法。当子类需要修改父类方法的行为时,可以使用方法重写。

(一)方法重写的规则

1. 方法签名必须一致:方法名、参数列表和返回类型必须与父类方法完全相同。Java 5 及以上版本允许返回类型可以是父类方法返回类型的子类。

2. 访问权限不能更严格:子类方法的访问权限必须与父类一致或更宽松。

3. 不能抛出更广泛的异常:子类方法不能抛出比父类方法更广泛的受检异常。

4. private、final 和 static 方法不能被重写:private 方法在类内部可见,不能被其他类访问;final 关键字用于防止方法被修改;而 static 方法属于类本身,不能被重写。当在子类中定义一个与父类同名的 static 方法时,实际上是在子类中定义了一个新的静态方法,而不是重写了父类的静态方法。

(二)方法重写的注意事项

建议在重写方法上添加 @Override 注解,编译器会检查方法是否正确重写,避免拼写错误或参数不匹配。

(三)方法重写实例

class Shape {
    void draw() {
        System.out.println("Drawing a shape");
    }
}
class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a circle");
    }
}
class Square extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a square");
    }
}
public class PolymorphismExample {
    public static void main(String[] args) {
        Shape shape1 = new Circle();
        Shape shape2 = new Square();
        shape1.draw(); // 输出 "Drawing a circle"
        shape2.draw(); // 输出 "Drawing a square"
    }
}

六、构造方法

构造方法用于对象的创建与初始化,方法名与类名相同且无返回类型。

根据 Java 的继承机制,子类的构造方法必须先调用父类的构造方法,再执行自己。这是因为子类需要继承父类的属性,而父类的构造方法负责初始化这些属性。

如果子类没有显式调用父类的构造方法,编译器会自动隐式调用父类的无参构造方法,即 super() 。在这种情况下,父类必须存在无参构造方法,否则编译会报错。

如果父类只定义了有参构造方法,子类必须显式调用父类的有参构造方法,即super(参数)。否则编译会报错。

例1:父类存在无参构造方法

class Person {
    protected String name;
    protected int age;
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Student extends Person {
    protected int score;
    public Student(String name, int age, int score) {
        this.score = score; // 先执行super(),再执行这句代码
    }
}

例2:父类只定义了有参构造方法

class Person {
    protected String name;
    protected int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Student extends Person {
    protected int score;
    public Student(String name, int age, int score) {
        super(name, age); // 父类只定义了有参构造方法,子类必须显式调用父类的有参构造方法
        this.score = score;
    }
}

七、this 和 super 关键字

this 指向当前对象本身;用于访问本类的属性和方法、调用本类其他构造方法;

super 指向当前对象的父类对象;用于访问父类的属性和方法、调用父类构造方法。

class Animal {
    void eat() {
        System.out.println("吃饭");
    }
}
class Dog extends Animal {
    @Override
    void eat() {
        System.out.println("啃骨头");
    }
    void eatTest() {
        super.eat();  // 调用父类方法
        this.eat();   // 调用自己的方法,可以省略this,只写eat()
    }
}

八、继承的优缺点

(一)优点

1. 实现代码复用,减少代码冗余。

2. 便于系统的扩展和维护。

3. 实现多态性,增强程序的灵活性。

(二)缺点

1. 过度使用继承会导致类之间的耦合度增加,降低代码的可维护性。

2. 父类的修改可能会对多个子类产生影响。

九、区分继承和组合

在使用继承时,我们要注意逻辑一致性。

比如,Book 类和 Student 类都有 name 属性,那么,我们能不能让 Student 继承自 Book 呢?

显然,从逻辑上讲,这是不合理的。究其原因,是因为 Student 与 Person 是 is 关系,而 Student 与 Book 是 has 关系。

具有 has 关系不应该使用继承,而是使用组合,即 Student 可以持有一个 Book 实例:

class Student extends Person {
    protected Book book;
    protected int score;
}

继承是 “is-a” 关系(例如,学生是人),而组合是 “has-a” 关系(例如,学生有书)。在设计类时,要优先考虑组合而非继承,这样可以降低类之间的耦合度。