多态
多态
多态性的字典定义指生物学中的一种原理,即生物体或物种可呈现多种形态或发育阶段。该原理同样适用于面向对象编程及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 示例可知,MountainBike 是 Bicycle 的子类。以下是 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 类的不可变类时尤其有用。