1. 概述

继承作为面向对象编程的核心原则之一,让我们能够复用现有代码或扩展现有类型

简单来说,在Java中:

  • 类可以继承另一个类和多个接口
  • 接口可以继承其他接口

本文将首先阐述继承的必要性,接着深入探讨类与接口的继承机制。然后分析变量/方法名称和访问修饰符如何影响成员继承,最后解释类型继承的实际意义。

2. 为什么需要继承

想象你作为汽车制造商,为客户提供多种车型。尽管不同车型可能有特殊功能(如天窗或防弹玻璃),但它们都包含基础组件(如发动机和车轮)。

与其为每个车型从头设计,不如创建基础设计并扩展出专业版本——这正是继承的核心思想。

通过继承,我们可以:

  1. 创建包含基础功能的基类
  2. 通过子类扩展出专业版本
  3. 接口同理,可扩展现有接口

术语对照

  • 基类型 = 超类/父类
  • 派生类型 = 扩展类/子类

3. 类的继承

3.1. 扩展类

类可通过extends关键字继承另一个类并定义新成员。先定义基类Car

public class Car {
    int wheels;
    String model;
    void start() {
        // 检查核心部件
    }
}

子类ArmoredCar继承Car

public class ArmoredCar extends Car {
    int bulletProofWindows;
    void remoteStartCar() {
        // 通过遥控启动车辆
    }
}

关键规则: ✅ Java类支持单继承ArmoredCar不能同时继承多个类) ✅ 未显式继承时,默认继承java.lang.Object ✅ 子类继承父类的:

  • protectedpublic成员
  • 同包下的default(包私有)成员 ❌ 不继承:
  • private成员
  • static成员

3.2. 子类访问父类成员

直接使用继承的成员即可,无需父类引用:

public class ArmoredCar extends Car {
    public String registerModel() {
        return model; // 直接访问父类字段
    }
}

4. 接口继承

4.1. 实现多个接口

虽然类只能单继承,但可同时实现多个接口。假设为特工的ArmoredCar添加飞行和漂浮功能:

public interface Floatable {
    void floatOnWater();
}
public interface Flyable {
    void fly();
}
public class ArmoredCar extends Car implements Floatable, Flyable {
    public void floatOnWater() {
        System.out.println("我能漂浮!");
    }
 
    public void fly() {
        System.out.println("我能飞行!");
    }
}

注意使用implements关键字实现接口。

4.2. 多继承的坑

Java通过接口支持多继承,但需注意:

Java 7之前:接口只能定义抽象方法,同名方法签名无冲突。

Java 8+的陷阱:接口可定义default方法实现。当实现多个含相同方法签名的接口时:

public interface Floatable {
    default void repair() {
        System.out.println("维修漂浮设备");
    }
}
public interface Flyable {
    default void repair() {
        System.out.println("维修飞行设备");
    }
}
public class ArmoredCar extends Car implements Floatable, Flyable {
    // 编译失败!必须重写repair()
}

⚠️ 解决方案:显式重写冲突方法。

同名常量冲突

public interface Floatable {
    int duration = 10;
}
public interface Flyable {
    int duration = 20;
}
public class ArmoredCar extends Car implements Floatable, Flyable {
    public void aMethod() {
        // System.out.println(duration); // 编译错误
        System.out.println(Floatable.duration); // 输出10
        System.out.println(Flyable.duration);   // 输出20
    }
}

4.3. 接口继承接口

接口可继承多个接口(使用extends关键字):

public interface Floatable {
    void floatOnWater();
}
public interface Flyable {
    void fly();
}
public interface SpaceTraveller extends Floatable, Flyable {
    void remoteControl();
}

区别:类用implements实现接口,接口用extends继承接口。

5. 类型继承

继承不仅传递成员,还传递类型。这让我们能面向接口/基类编程,而非具体实现。

例如,公司维护员工车辆列表(车型各异):

public class Employee {
    private String name;
    private Car car; // 使用基类类型
    
    // 标准构造器
}

所有Car子类都继承Car类型,因此可用基类引用指向子类实例:

Employee e1 = new Employee("张伟", new ArmoredCar());
Employee e2 = new Employee("李娜", new SpaceCar());
Employee e3 = new Employee("王芳", new BMW());

6. 成员隐藏

6.1. 实例成员隐藏

当父子类存在同名变量/方法时:

public class ArmoredCar extends Car {
    private String model;
    public String getAValue() {
        return super.model;   // 返回父类Car的model
        // return this.model; // 返回ArmoredCar的model
        // return model;      // 默认返回ArmoredCar的model
    }
}

技巧:用super明确访问父类成员,this访问当前类成员。

6.2. 静态成员隐藏

静态成员属于类而非实例,处理方式不同:

public class Car {
    public static String msg() {
        return "Car";
    }
}
public class ArmoredCar extends Car {
    public static String msg() {
        return super.msg(); // 编译错误!静态方法中不能用super
    }
}

正确姿势

return Car.msg(); // 通过类名调用

同名静态方法调用示例:

Car first = new ArmoredCar();
ArmoredCar second = new ArmoredCar();

first.msg();    // 输出"Car"(变量类型决定)
second.msg();   // 输出"ArmoredCar"(实际类型决定)

本质:静态方法调用取决于引用变量的声明类型

7. 总结

本文深入探讨了Java继承的核心机制:

  • 类支持单继承,接口支持多继承
  • 继承传递成员和类型
  • 成员隐藏规则需特别注意(静态/实例差异)
  • 多继承场景需处理方法冲突

掌握这些概念能帮你写出更灵活的代码,避免常见陷阱。完整示例代码见GitHub仓库


原始标题:Guide to Inheritance in Java