继承
继承
在前面的部分中,您已经多次看到继承的提及。在 Java 语言中,类可以从其他类派生,从而继承这些类的字段和方法。
定义:从另一个类派生的类称为子类(也称为派生类、扩展类或子类)。派生子类的类称为超类(也称为基类或父类)。
除了
Object
(没有超类)之外,每个类只有一个直接超类(单继承)。在没有其他显式超类的情况下,每个类隐式地是Object
的子类。类可以从派生自类的类派生,这些类又可以从派生自类的类派生,依此类推,最终派生自最顶层的类
Object
。据说这样的类是从继承链中所有类派生的,这些类一直追溯到Object
。
继承的概念很简单但很强大:当您想要创建一个新类,并且已经有一个类包含您想要的一些代码时,您可以从现有类派生新类。通过这样做,您可以重用现有类的字段和方法,而无需自己编写(和调试!)它们。
子类继承其超类中的所有成员(字段、方法和嵌套类)。构造函数不是成员,因此不会被子类继承,但超类的构造函数可以从子类中调用。
在 java.lang
包中定义的 Object
类定义并实现所有类(包括您编写的类)的通用行为。在 Java 平台中,许多类直接从 Object
派生,其他类从这些类中的一些类派生,依此类推,形成一个类层次结构。
在层次结构的顶部,Object
是所有类中最通用的类。层次结构底部附近的类提供更专门的行为。
继承的示例
以下是类和对象部分中介绍的 Bicycle
类的可能实现的示例代码
public class Bicycle {
// the Bicycle class has three fields
public int cadence;
public int gear;
public int speed;
// the Bicycle class has one constructor
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
// the Bicycle class has four methods
public void setCadence(int newValue) {
cadence = newValue;
}
public void setGear(int newValue) {
gear = newValue;
}
public void applyBrake(int decrement) {
speed -= decrement;
}
public void speedUp(int increment) {
speed += increment;
}
}
MountainBike
类的类声明,它是 Bicycle
的子类,可能如下所示
public class MountainBike extends Bicycle {
// the MountainBike subclass adds one field
public int seatHeight;
// the MountainBike subclass has one constructor
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
// the MountainBike subclass adds one method
public void setHeight(int newValue) {
seatHeight = newValue;
}
}
MountainBike
继承了 Bicycle
的所有字段和方法,并添加了字段 seatHeight
和一个用于设置它的方法。除了构造函数之外,就好像您完全从头开始编写了一个新的 MountainBike
类,它有四个字段和五个方法。但是,您不必做所有工作。如果 Bicycle
类中的方法很复杂并且需要大量时间进行调试,这将特别有用。
您可以在子类中做什么
子类继承其父类的所有 public
和 protected
成员,无论子类位于哪个包中。如果子类与父类位于同一个包中,它还继承父类的包私有成员。您可以按原样使用继承的成员,替换它们,隐藏它们,或用新成员补充它们
- 继承的字段可以直接使用,就像任何其他字段一样。
- 您可以在子类中声明一个与超类中相同的名称的字段,从而隐藏它(不推荐)。
- 您可以在子类中声明超类中不存在的新字段。
- 继承的方法可以直接使用。
- 您可以在子类中编写一个新的实例方法,该方法具有与超类中相同的签名,从而覆盖它。
- 您可以在子类中编写一个新的静态方法,该方法具有与超类中相同的签名,从而隐藏它。
- 您可以在子类中声明超类中不存在的新方法。
- 您可以编写一个子类构造函数,该构造函数隐式或使用关键字 super 调用超类的构造函数。
- 本课的以下部分将扩展这些主题。
超类中的私有成员
子类不继承其父类的私有成员。但是,如果超类具有用于访问其私有字段的公共或受保护的方法,这些方法也可以被子类使用。
嵌套类可以访问其封闭类的所有私有成员——包括字段和方法。因此,子类继承的公共或受保护的嵌套类可以间接访问超类的所有私有成员。
强制转换对象
我们已经看到,对象的数据类型与其实例化的类相同。例如,如果我们编写
public MountainBike myBike = new MountainBike();
那么 myBike
的类型为 MountainBike
。
MountainBike
是从 Bicycle
和 Object
派生的。因此,MountainBike
是一个 Bicycle
,也是一个 Object
,它可以在需要 Bicycle
或 Object
对象的地方使用。
反之不一定成立:Bicycle
可能是 MountainBike
,但也不一定。类似地,Object
可能是 Bicycle
或 MountainBike
,但也不一定。
强制转换显示了在继承和实现允许的对象之间,使用一种类型对象代替另一种类型对象。例如,如果我们编写
Object obj = new MountainBike();
那么 obj
既是 Object
又是 MountainBike
(直到 obj
被分配另一个不是 MountainBike
的对象)。这称为隐式强制转换。
另一方面,如果我们编写
MountainBike myBike = obj;
我们将得到一个编译时错误,因为编译器不知道 obj
是一个 MountainBike
。但是,我们可以通过显式强制转换告诉编译器,我们承诺将 MountainBike
分配给 obj
MountainBike myBike = (MountainBike)obj;
此强制转换插入一个运行时检查,以验证 obj
是否分配了一个 MountainBike
,以便编译器可以安全地假设 obj
是一个 MountainBike
。如果 obj
在运行时不是一个 MountainBike
,则会抛出异常。
注意:您可以使用
instanceof
运算符对特定对象的类型进行逻辑测试。这可以避免由于强制转换不当而导致的运行时错误。例如
if (obj instanceof MountainBike) {
MountainBike myBike = (MountainBike)obj;
}
这里
instanceof
运算符验证obj
是否引用MountainBike
,以便我们可以进行强制转换,并知道不会抛出运行时异常。
状态、实现和类型的多重继承
类和接口之间的一个显著区别是,类可以有字段,而接口不能。此外,您可以实例化一个类以创建对象,而不能对接口执行此操作。如“什么是对象?”部分所述,对象在字段中存储其状态,这些字段在类中定义。Java 编程语言不允许您扩展多个类的其中一个原因是避免状态多重继承的问题,即从多个类继承字段的能力。例如,假设您能够定义一个扩展多个类的新的类。当您通过实例化该类来创建对象时,该对象将继承其所有超类的字段。如果来自不同超类的
实现的多重继承是指从多个类继承方法定义的能力。这种多重继承会带来一些问题,例如命名冲突和歧义。当支持这种多重继承的编程语言的编译器遇到包含相同名称方法的超类时,它们有时无法确定要访问或调用的成员或方法。此外,程序员可能会无意中通过向超类添加新方法来引入命名冲突。默认方法引入了实现多重继承的一种形式。一个类可以实现多个接口,这些接口可以包含具有相同名称的默认方法。Java 编译器提供了一些规则来确定特定类使用哪个默认方法。
Java 编程语言支持类型多重继承,即一个类可以实现多个接口的能力。一个对象可以有多种类型:它自身类的类型以及它所实现的所有接口的类型。这意味着,如果一个变量被声明为接口的类型,那么它的值可以引用从实现该接口的任何类实例化的任何对象。这将在“使用接口作为类型”一节中讨论。
与实现多重继承一样,一个类可以继承在它扩展的接口中定义的不同方法实现(作为default
或static
)。在这种情况下,编译器或用户必须决定使用哪一个。
最后更新: 2021 年 9 月 14 日