2

继承

在前面的课程中,你已经多次看到了继承,在Java语言中,类可以从其他类派生,从而从这些类继承字段和方法。

定义:从另一个类派生的类称为子类(也是派生类,扩展类或子类),派生子类的类称为超类(也是基类或父类)。

除了Object没有超类,每个类都有一个且只有一个直接超类(单继承),在没有任何其他显式超类的情况下,每个类都隐式地是Object的子类。

类可以从派生自类的类派生的类派生,依此类推,最终派生自最顶层的类,Object,这样的类被称为继承链中所有向后延伸到Object的类的子类。

继承的概念很简单但很强大:当你想要创建一个新类并且已经有一个包含你想要的一些代码的类时,你可以从现有类派生你的新类,在这样做时,你可以重用现有类的字段和方法,而无需自己编写(和调试)它们。

子类从其超类继承所有成员(字段、方法和嵌套类),构造函数不是成员,因此它们不是由子类继承的,但是可以从子类调用超类的构造函数。

Java平台类层次结构

java.lang包中定义的Object类定义并实现所有类共有的行为 — 包括你写的那些,在Java平台中,许多类直接从Object派生,其他类派生自其中一些类,依此类推,形成类的层次结构。

classes-object.gif

在层次结构的顶部,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;
    }
        
}

作为Bicycle的子类的MountainBike类的类声明可能如下所示:

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来调用超类的构造函数。

本课程的以下部分将扩展这些主题。

超类中的私有成员

子类不继承其父类的private成员,但是,如果超类具有访问其私有字段的公共或受保护方法,则子类也可以使用这些方法。

嵌套类可以访问其封闭类的所有私有成员 — 包括字段和方法,因此,子类继承的publicprotected嵌套类可以间接访问超类的所有私有成员。

转换对象

我们已经看到一个对象是实例化它的类的数据类型,例如,如果我们写

public MountainBike myBike = new MountainBike();

那么myBikeMountainBike类型。

MountainBikeBicycleObject的后代,因此,MountainBike是一个Bicycle并且也是一个Object,它可以在需要BicycleObject对象的任何地方使用。

反过来不一定是对的:Bicycle可能是MountainBike,但不一定。类似地,Object可以是Bicycle或山MountainBike,但不一定如此。

转换显示在继承和实现允许的对象中使用一种类型的对象代替另一种类型的对象,例如,如果我们写

Object obj = new MountainBike();

那么obj既是Object又是MountainBike(直到obj被赋予另一个不是MountainBike的对象的时候),这称为隐式转换。

另一方面,如果我们写

MountainBike myBike = obj;

我们会得到编译时错误,因为编译器不知道objMountainBike,但是,我们可以告诉编译器我们承诺通过显式转换将MountainBike分配给obj

MountainBike myBike = (MountainBike)obj;

此强制转换插入运行时检查,为obj分配MountainBike,以便编译器可以安全地假设objMountainBike,如果obj在运行时不是MountainBike,则会抛出异常。

注意:你可以使用instanceof运算符对特定对象的类型进行逻辑测试,这可以避免由于转换不当而导致的运行时错误,例如:

if (obj instanceof MountainBike) {
    MountainBike myBike = (MountainBike)obj;
}

这里,instanceof运算符验证obj是否引用了MountainBike,以便我们可以知道不会抛出运行时异常来进行转换。

状态、实现和类型的多重继承

类和接口之间的一个显着区别是类可以有字段而接口不能,此外,你可以实例化一个类来创建一个对象,这是你无法使用接口进行的,如什么是对象?部分所述,对象将其状态存储在字段中,这些字段在类中定义。Java编程语言不允许扩展多个类的一个原因是避免了多重继承状态的问题,即从多个类继承字段的能力。例如,假设你能够定义一个扩展多个类的新类,通过实例化该类来创建对象时,该对象将继承所有类的超类中的字段,如果来自不同超类的方法或构造函数实例化相同的字段会怎样?哪个方法或构造函数优先?由于接口不包含字段,因此你不必担心多重继承状态所导致的问题。

实现的多重继承是从多个类继承方法定义的能力,这种类型的多重继承会出现问题,例如名称冲突和歧义,当支持这种类型的多继承的编程语言的编译器遇到包含具有相同名称的方法的超类时,它们有时无法确定要访问或调用的成员或方法。此外,程序员可以通过向超类添加新方法而无意中引入名称冲突,默认方法引入了一种实现的多重继承形式,一个类可以实现多个接口,该接口可以包含具有相同名称的默认方法,Java编译器提供了一些规则来确定特定类使用哪种默认方法。

Java编程语言支持多种类型的继承,这是类实现多个接口的能力,对象可以有多种类型:它自己的类的类型以及该类实现的所有接口的类型。这意味着如果将变量声明为接口的类型,则其值可以引用从实现接口的任何类实例化的任何对象,这在将接口用作类型一节中讨论。

与多实现继承一样,一个类可以继承它扩展的接口中定义的方法的不同实现(作为默认或静态),在这种情况下,编译器或用户必须决定使用哪一个。

隐藏字段

在类中,与超类中的字段具有相同名称的字段会隐藏超类的字段,即使它们的类型不同,在子类中,超类中的字段不能通过其简单名称引用,相反,必须通过super访问该字段,一般来说,我们不建议隐藏字段,因为它使代码难以阅读。

编写Final类和方法

你可以声明一些或所有类的方法为final,你在方法声明中使用final关键字来指示子类不能重写该方法,Object类这样做 — 它的一些方法是final

如果方法具有不应被更改的实现,并且对于对象的一致状态至关重要,则可能希望将方法设为final,例如,你可能希望在此ChessAlgorithm类中生成getFirstPlayer方法:

class ChessAlgorithm {
    enum ChessPlayer { WHITE, BLACK }
    ...
    final ChessPlayer getFirstPlayer() {
        return ChessPlayer.WHITE;
    }
    ...
}

从构造函数调用的方法通常应该声明为final,如果构造函数调用非final方法,子类可能会重新定义该方法,并产生意外或不希望看到的结果。

请注意,你还可以声明整个类final,声明为final的类不能被子类化,这特别有用,例如,在创建String类这样的不可变类时。

继承总结

除了Object类之外,一个类只有一个直接超类,类继承其所有超类中的字段和方法,无论是直接还是间接,子类可以重写它继承的方法,也可以隐藏它继承的字段或方法(请注意,隐藏字段通常是糟糕的编程习惯)。

“覆盖和隐藏方法”部分中的表显示了使用与超类中的方法相同的签名声明方法的效果。

Object类是类层次结构的顶部,所有类都是此类的后代,并从中继承方法,从Object继承的有用方法包括toString()equals()clone()getClass()

你可以通过在类的声明中使用final关键字来阻止类被子类化,同样,你可以通过将方法声明为final方法来防止子类重写该方法。

抽象类只能被子类化,它无法实例化,抽象类可以包含抽象方法 — 声明但未实现的方法,然后,子类提供抽象方法的实现。


上一篇:默认方法
下一篇:重写和隐藏方法

博弈
2.5k 声望1.5k 粉丝

态度决定一切