多态

多态

多态性的字典定义指生物学中的一种原理,即生物体或物种可呈现多种形态或发育阶段。该原理同样适用于面向对象编程及Java等语言。类的子类可定义自身独特的行为,同时共享父类的部分功能。
通过对 Bicycle 类进行微小修改即可演示多态性。例如,可以在该类中添加一个printDescription()方法,用于显示实例当前存储的所有数据。

public void printDescription(){
    IO.println("\nBike is " + "in gear " + this.gear
        + " with a cadence of " + this.cadence +
        " and travelling at a speed of " + this.speed + ". ");
}

为展示Java语言的多态特性,请在Bicycle类基础上扩展MountainBike和RoadBike类。对于MountainBike类,需新增一个表示悬挂系统的字段,该字段为字符串类型,用于标识自行车是否配备前减震器(取值为Front),或同时配备前后减震器(取值为Dual)。
下面是更新后的类

public class MountainBike extends Bicycle {
    private String suspension;

    public MountainBike(
               int startCadence,
               int startSpeed,
               int startGear,
               String suspensionType){
        super(startCadence,
              startSpeed,
              startGear);
        this.setSuspension(suspensionType);
    }

    public String getSuspension(){
      return this.suspension;
    }

    public void setSuspension(String suspensionType) {
        this.suspension = suspensionType;
    }

    public void printDescription() {
        super.printDescription();
        IO.println("The " + "MountainBike has a" +
            getSuspension() + " suspension.");
    }
} 

请注意被重写的 printDescription() 方法。除了之前提供的信息外,输出中还包含了关于该悬挂的额外数据。
接下来创建RoadBike类。由于公路车或竞速自行车采用窄胎设计,需添加属性来追踪轮胎宽度。以下是RoadBike类的定义:

public class RoadBike extends Bicycle{
    // In millimeters (mm)
    private int tireWidth;

    public RoadBike(int startCadence,
                    int startSpeed,
                    int startGear,
                    int newTireWidth){
        super(startCadence,
              startSpeed,
              startGear);
        this.setTireWidth(newTireWidth);
    }

    public int getTireWidth(){
      return this.tireWidth;
    }

    public void setTireWidth(int newTireWidth){
        this.tireWidth = newTireWidth;
    }

    public void printDescription(){
        super.printDescription();
        IO.println("The RoadBike" + " has " + getTireWidth() +
            " MM tires.");
    }
}

请注意,printDescription() 方法再次被重写。这次,它会显示轮胎宽度的信息。
简而言之,共有三类自行车: Bicycle, MountainBike, 和 RoadBike。其中两个子类重写了 printDescription() 方法,用于输出各自的独特信息。
以下是一个测试程序,它创建了三个名为 Bicycle 的变量。每个变量被赋值给三个自行车类中的一个,随后每个变量都会被打印出来。

public class TestBikes {
  public static void main(String[] args){
    Bicycle bike01, bike02, bike03;

    bike01 = new Bicycle(20, 10, 1);
    bike02 = new MountainBike(20, 10, 5, "Dual");
    bike03 = new RoadBike(40, 20, 8, 23);

    bike01.printDescription();
    bike02.printDescription();
    bike03.printDescription();
  }
}

输出如下:

Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10.

Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10.
The MountainBike has aDual suspension.

Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20.
The RoadBike has 23 MM tires.

Java虚拟机(JVM)会为每个变量所引用的对象调用相应的方法,而非调用该变量类型定义的方法。这种行为被称为虚拟方法调用,体现了Java语言中重要的多态特性之一。

隐藏字段

在类内部,若某个字段与父类中同名字段存在,则会隐藏父类的字段——即使两者类型不同。在子类中,无法直接通过简单名称引用父类的字段,必须通过super访问(具体将在下一节说明)。通常我们不建议隐藏字段,因为这会降低代码的可读性。

使用super关键字

访问父类成员

如果你的方法重写了父类中的某个方法,你可以通过使用关键字 super 来调用被重写的方法。你也可以使用 super 来引用隐藏字段(尽管不建议隐藏字段)。考虑这个父类 Superclass

public class Superclass {
    public void printMethod() {
        IO.println("Printed in Superclass.");
    }
}

这里有一个名为 Subclass 的子类,它重写了 printMethod() 方法:

public class Subclass extends Superclass {
    // overrides printMethod in Superclass
    public void printMethod() {
        super.printMethod();
        IO.println("Printed in Subclass");
    }
    public static void main(String[] args) {
        Subclass s = new Subclass();
        s.printMethod();    
    }
}

在子类中,简单名称 printMethod() 指代子类中声明的方法,该方法覆盖了父类中的同名方法。因此,要引用从父类继承的 printMethod(),子类必须使用限定名称,如示例中所示使用 super。编译并执行子类将输出以下内容:

Printed in Superclass.
Printed in Subclass

子类构造函数

以下示例演示了如何使用 super 关键字调用父类的构造函数。回顾 Bicycle 示例可知,MountainBikeBicycle 的子类。以下是 MountainBike(子类)的构造函数,它先调用父类构造函数,然后添加自身的初始化代码:

public MountainBike(int startHeight, 
                    int startCadence,
                    int startSpeed,
                    int startGear) {
    super(startCadence, startSpeed, startGear);
    seatHeight = startHeight;
} 

调用父类构造函数的代码必须放在子类构造函数的第一行。调用父类构造函数的语法是:

super();

或者

super(parameter list);

使用 super() 时,将调用父类的无参构造函数。使用 super(parameter list) 时,将调用父类中参数列表匹配的构造函数。

注意:如果构造函数未显式调用父类构造函数,Java编译器会自动插入对父类无参构造函数的调用。若父类不存在无参构造函数,则会引发编译时错误。由于Object类确实存在此类构造函数,因此当Object是唯一父类时不会出现问题。

如果子类的构造函数显式或隐式地调用了其父类的构造函数,你可能会认为会触发一整条构造函数调用链,一直追溯到Object类的构造函数。事实上确实如此。这种机制称为构造函数链式调用,当类继承关系链较长时,你需要对此保持警惕。

编写 Final Classes 和 Methods

你可以将类中部分或全部方法声明为 final 。在方法声明中使用 final 关键字,表示该方法不能被子类覆盖。Object类就是如此——其许多方法都是final的。

当某个方法的实现不应被更改且对对象的状态一致性至关重要时,您可能希望将其声明为 final 方法。例如,您可能需要将ChessAlgorithm 类中的 getFirstPlayer() 方法声明为 final

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

构造函数调用的方法通常应声明为final。若构造函数调用了非final方法,子类可能重定义该方法,导致出人意料或不可预期的结果。
请注意,您也可以将整个类声明为 final。被声明为 final 的类无法被继承。这在创建类似 String 类的不可变类时尤其有用。