继承是“是一个”式的复用
继承让新类可以在已有类的基础上构建。新类叫子类,它自动获得父类的字段和方法,再按需添加或修改。当一种类型是另一种类型的更具体的种类时,就该用继承:Dog 是一种 Animal,SavingsAccount 是一种 BankAccount。
继承用 extends 关键字来写。父类中所有 public 或 protected 的成员,子类都可以直接使用,不用重写一遍。
Dog 从来没有声明 name 或 eat(),却两者都有。这正是关键所在:共享的行为只放在一个地方。
super:调用父类
子类的构造函数必须确保父类先完成初始化。做法是使用 super(...),它调用父类的构造函数,并且必须是子类构造函数中的第一条语句。如果你省略它,Java 会悄悄插入一次对父类无参构造函数的调用;而如果父类没有这样的构造函数,代码就无法编译。
输出显示,父类构造函数在子类构造函数体之前运行:对象的构造从继承层次的顶部往下进行。
重写方法
子类可以用相同的签名重新定义继承来的方法,从而替换它。这叫重写,你应该始终用 @Override 标注它。这个注解不是必需的,但它会让编译器确认你确实匹配了父类的某个方法。它能发现像把 toString() 写成 tostring() 这样的拼写错误,否则这种错误会悄悄地创建一个全新的方法。
虽然数组的类型是 Animal,每个元素运行的却是它自己的 speak()。Java 在运行时根据真实的对象来选择方法,而不是根据变量声明的类型,这就是多态的基础。
用 super.method() 调用父类的版本
重写并不一定要丢掉父类的工作。用 super.method() 运行继承来的版本,然后再加上你自己的逻辑:
如果没有 super.,在 TimestampLogger.log 里调用 log 就会调用它自己,无限递归下去。super. 明确表示“父类的版本”。
继承的字段与访问权限
子类能看到父类的 public 和 protected 成员,但看不到它的 private 成员。private 字段仍然存在于对象中,父类自己的方法可以访问它们,但子类不能直接引用。当你希望子类可以访问某个成员、同时又对无关代码隐藏它时,就用 protected,它是 Java 的访问修饰符之一。
class Base {
private int secret; // invisible to subclasses
protected int shared; // visible to subclasses
}
class Derived extends Base {
void demo() {
shared = 5; // OK
// secret = 5; // compile error - private to Base
}
}
这也是为什么子类的构造函数经常必须调用 super(...):这是初始化父类私有状态的唯一途径。
用 final 阻止继承
有时一个类根本不应该被继承,String 就是出于这个原因被声明为 final 的。把类标记为 final 会禁止子类化;把方法标记为 final 会禁止重写这个方法,但类本身仍然可以被继承。
final class Constants { } // cannot be subclassed
class Config {
final void load() { } // subclasses may extend Config
// but cannot override load()
}
当一个类的行为必须在整个程序中得到保证、不可改变时,就用 final。它是一个有意为之的“不要继承”信号,而不是默认做法。
常见陷阱:不是“是一个”关系时,优先用组合
继承很诱人,因为它能复用代码,但它会把子类和父类紧紧绑在一起。如果两者之间不是真正的“是一个”关系,比如一辆 Car 恰好需要一个 Engine,就不要写 Car extends Engine。汽车拥有一个引擎,而不是一个引擎。应该改用字段(组合)来建模:
class Car {
private Engine engine = new Engine(); // Car HAS-A Engine
void start() { engine.ignite(); }
}
只有当子类确实是父类的一种特殊形式,并且你既想继承又想替换它的行为时,才使用继承。
下一篇:接口
用 extends 实现的继承给你一个父类和共享的实现。但一个类只能继承一个类,那么如何让彼此无关的类拥有共同的能力呢?这正是接口的用途:一份可以被许多类实现的契约,也是下一篇的主题。
常见问题
Java 中的继承是什么?
继承让一个类(子类)通过 extends 关键字复用另一个类(父类)的字段和方法。子类自动获得父类的 public 和 protected 成员,可以添加新成员,也可以通过重写替换继承来的行为。它描述的是“是一个”(is-a)关系:Dog 是一种 Animal。
Java 中的 super 关键字有什么作用?
super 指向父类。构造函数中的 super(...) 调用父类的构造函数(必须是第一条语句),而 super.method() 调用你已重写的方法在父类中的版本。它让子类可以在父类逻辑的基础上扩展,而不是把它完全替换掉。
Java 中重写和重载有什么区别?
重写(overriding)是在子类中用相同的签名重新定义继承来的方法,从而改变其行为,要用 @Override 标注。重载(overloading)是在同一个类中定义多个同名但参数列表不同的方法。重写关乎继承和运行时分派;重载只是两个碰巧同名的方法。