Java 继承的详细教学
·
关于 Java 继承的详细教学,涵盖概念、语法、重点、易错点、考点及实操示例。
Java 继承详解
继承 (Inheritance) 是面向对象编程 (OOP) 的四大基本特性之一(封装、继承、多态、抽象)。它允许我们基于已有的类创建新的类。新的类(称为子类或派生类)继承了已有类(称为父类或基类或超类)的属性和方法,并且可以添加自己新的属性和方法,或者对父类的方法进行重新定义(覆盖)。
一、 为什么使用继承?
- 代码重用 (Code Reusability): 避免重复编写父类中已有的代码,提高开发效率。
- 可维护性 (Maintainability): 当需要修改父类的功能时,所有继承它的子类都会自动获得更新(除非子类覆盖了该方法)。
- 建立类层次结构 (Class Hierarchy): 模拟现实世界中“是一个(Is-A)”的关系(如:狗是一个动物,轿车是一辆车)。
- 实现多态 (Polymorphism): 继承是实现运行时多态的基础。
二、 继承的语法
class SubclassName extends SuperclassName {
// 子类特有的字段和方法
// 可以覆盖父类的方法
}
extends是 Java 中用于实现继承的关键字。SubclassName是你定义的子类名称。SuperclassName是父类的名称。- Java 是单继承,一个类只能直接继承自一个父类。但可以通过多层继承形成继承链(树形结构)。
- Java 中所有类都直接或间接继承自
java.lang.Object类(根类)。
三、 核心概念与重点
-
访问修饰符 (Access Modifiers) 与继承:
public: 任何地方都可以访问。protected: 同一包内或不同包的子类中可以访问。默认 (default): 同一包内可以访问。子类在不同包则不能访问。private: 仅在定义它的类内部访问。子类不能直接访问父类的private成员!- 重点: 子类继承父类时,只能访问父类的
public和protected成员(以及同一包下的默认成员)。private成员对子类不可见。
-
方法覆盖/重写 (Method Overriding):
- 概念: 子类可以提供一个与父类方法签名(方法名 + 参数列表)完全相同的方法实现。当通过子类对象调用该方法时,执行的是子类覆盖后的方法,而不是父类的方法。
- 规则:
- 方法名和参数列表必须与父类方法完全相同。
- 返回类型可以是父类方法返回类型的子类(协变返回类型,Java 5+ 支持)。
- 访问权限不能比父类方法更严格(可以相同或更宽松)。
- 不能覆盖
private方法(因为不可见)。 - 不能覆盖
final方法。 - 不能覆盖
static方法(静态方法属于类,与对象无关,不存在覆盖,是隐藏)。 - 可以覆盖
abstract方法(必须提供实现)。 - 使用
@Override注解(可选但强烈推荐): 编译器会检查该方法是否确实覆盖了父类方法,避免因拼写错误等导致意外的重载而非重写。
- 目的: 实现子类特有的行为,是实现多态的关键。
-
super关键字:- 作用: 用于在子类中引用父类的成员(字段、方法、构造方法)。
- 调用父类方法:
super.methodName();。常用于在子类覆盖的方法中,先调用父类的方法完成基础功能,再添加子类特有的功能。 - 调用父类构造方法:
super(...);。必须放在子类构造方法的第一行。用于初始化从父类继承来的部分。如果父类有无参构造方法,编译器会自动在子类构造方法的第一行插入super();。
-
构造方法与继承:
- 创建子类对象时,必须先调用父类的构造方法来初始化父类继承下来的部分。
- 子类构造方法的第一行语句,如果没有显式地调用父类其他构造方法 (
super(...);),编译器会自动插入对父类无参构造方法 (super();) 的调用。 - 如果父类没有无参构造方法(只有带参构造方法),则子类构造方法中必须显式地用
super(...);调用父类的某个带参构造方法,否则编译错误。
四、 易错点与考点
private成员的“不可见性”: 子类对象拥有父类的private字段(内存中有),但不能在子类代码中直接通过名字访问它们。只能通过父类提供的public或protected的getter/setter方法来间接访问。super调用构造方法的位置:super(...);必须位于子类构造方法的第一行,否则编译错误。- 父类无无参构造方法: 如果父类定义了带参构造方法但没有定义无参构造方法,子类构造方法必须显式调用父类的带参构造方法 (
super(...);),否则编译失败。 - 方法覆盖 vs 方法重载 (Overloading):
- 覆盖 (Override): 发生在父子类之间,方法签名相同。
- 重载 (Overload): 发生在同一个类中,方法名相同但参数列表不同(个数、类型、顺序)。
- 考点:区分两者,避免混淆。
final与继承:final类:不能被继承。final方法:不能被覆盖。final变量:常量,值不能被修改。
static方法与继承: 静态方法属于类,不能被覆盖。如果子类定义了与父类签名相同的静态方法,这叫方法隐藏 (Method Hiding),不是覆盖。调用哪个方法取决于引用变量的类型,而不是实际对象的类型。- 继承链与
Object类: 理解所有类都继承自Object,熟悉Object类的重要方法(如toString(),equals(),hashCode()),并理解在子类中覆盖这些方法的必要性(尤其是集合类使用时)。
五、 实操示例
// 父类:交通工具
class Vehicle {
// protected 允许子类直接访问
protected String brand;
// 父类带参构造方法 (没有无参构造方法)
public Vehicle(String brand) {
this.brand = brand;
System.out.println("Vehicle constructor called. Brand: " + brand);
}
public void drive() {
System.out.println("Vehicle is driving.");
}
// 可以被覆盖的方法
public void honk() {
System.out.println("Vehicle honks!");
}
}
// 子类:汽车,继承自 Vehicle
class Car extends Vehicle {
private int numDoors;
// 子类构造方法,必须调用父类构造方法
public Car(String brand, int numDoors) {
super(brand); // 调用父类带参构造方法,必须放在第一行
this.numDoors = numDoors;
System.out.println("Car constructor called. Doors: " + numDoors);
}
// 覆盖父类的 honk 方法
@Override
public void honk() {
super.honk(); // 调用父类的 honk 方法
System.out.println("Car honks loudly: Beep Beep!");
}
// 子类特有的方法
public void openSunroof() {
System.out.println("Car opens sunroof.");
}
}
// 测试类
public class InheritanceDemo {
public static void main(String[] args) {
// 创建 Car 对象
Car myCar = new Car("Toyota", 4);
myCar.drive(); // 调用继承自父类的方法
myCar.honk(); // 调用子类覆盖后的方法 (输出父类+子类的信息)
myCar.openSunroof(); // 调用子类特有的方法
// 多态:父类引用指向子类对象
Vehicle vehicle = new Car("Honda", 2);
vehicle.drive(); // 调用的是 Vehicle 的 drive(),因为 Car 没有覆盖它
vehicle.honk(); // 调用的是 Car 的 honk()!因为运行时实际对象是 Car,且 honk 被覆盖了
// vehicle.openSunroof(); // 编译错误!Vehicle 类型没有 openSunroof 方法
}
}
输出示例:
Vehicle constructor called. Brand: Toyota
Car constructor called. Doors: 4
Vehicle is driving.
Vehicle honks!
Car honks loudly: Beep Beep!
Car opens sunroof.
Vehicle constructor called. Brand: Honda
Car constructor called. Doors: 2
Vehicle is driving.
Vehicle honks!
Car honks loudly: Beep Beep!
示例解析:
- 构造方法链: 创建
Car对象时,首先调用父类Vehicle的构造方法 (super("Toyota")),然后调用子类Car的构造方法。 - 方法继承:
Car对象可以直接调用父类Vehicle的drive()方法。 - 方法覆盖:
Car覆盖了Vehicle的honk()方法。在覆盖的方法中,使用super.honk()先调用了父类的方法,然后添加了子类特有的行为。 - 特有方法:
Car定义了父类没有的openSunroof()方法。 - 多态:
Vehicle vehicle = new Car("Honda", 2);体现了多态。vehicle引用是Vehicle类型,但指向的实际对象是Car类型。vehicle.drive(): 因为Car没有覆盖drive(),所以调用的是Vehicle的drive()。vehicle.honk(): 因为Car覆盖了honk(),所以运行时实际调用的是Car的honk()方法(多态的核心)。vehicle.openSunroof(): 编译错误,因为编译器只认Vehicle类型的方法列表,里面没有openSunroof()。如果想调用,需要向下转型((Car) vehicle).openSunroof();(存在ClassCastException风险)。
六、 总结与注意事项
- 理解“是一个”关系: 只有当子类和父类之间逻辑上存在“是一个”的关系时,才使用继承(例如
Car is a Vehicle)。不要为了复用代码而滥用继承。 - 优先使用组合而非继承: 如果两个类之间是“有一个”的关系(例如
Car has an Engine),应使用组合(在一个类中包含另一个类的实例)而不是继承。 - 谨慎使用继承层次: 过深的继承层次会增加系统复杂性,降低可维护性。
@Override是好习惯: 总是使用@Override注解来标记覆盖的方法,提高代码可读性和安全性。- 理解多态: 继承是实现多态的基础,多态极大地提高了代码的灵活性和可扩展性。
更多推荐



所有评论(0)