覆写和隐藏方法
实例方法
子类中具有与父类实例方法相同签名(名称、参数数量及类型)和返回类型的实例方法,将覆盖父类的方法。
子类能够覆写方法的能力,使得类能够从行为“足够接近”的父类继承,然后根据需要修改行为。覆写的方法与被覆写的方法具有相同的名称、参数数量和类型,以及返回类型。覆写的方法还可以返回被覆写的方法返回类型的子类型,这种子类型称为协变返回类型。
在覆写方法时,建议使用 @Override 注解告知编译器你打算覆写父类中的方法。若因某种原因编译器检测到该方法在父类中不存在,则会报错。有关 @Override 的更多信息,请参阅注解章节。
静态方法
如果子类定义了一个与父类中静态方法签名相同的静态方法,则子类中的方法会隐藏父类中的方法。
隐藏静态方法与覆写实例方法之间的区别具有重要意义:
- 被调用的覆写方法是子类中的那个。
- 被调用的隐藏静态方法的版本取决于调用方是父类还是子类。
- 考虑一个包含两个类的示例。第一个是 Animal 类,它包含一个实例方法和一个静态方法:
public class Animal {
public static void testClassMethod() {
IO.println("The static method in Animal");
}
public void testInstanceMethod() {
IO.println("The instance method in Animal");
}
}
第二个类是 Animal 类的子类,名为 Cat:
public class Cat extends Animal {
public static void testClassMethod() {
IO.println("The static method in Cat");
}
public void testInstanceMethod() {
IO.println("The instance method in Cat");
}
public static void main(String[] args) {
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod();
myAnimal.testInstanceMethod();
}
}
Cat类重写了Animal类的实例方法,并隐藏了Animal类的静态方法。该类中的main方法创建了一个Cat实例,并在类上调用testClassMethod方法,在实例上调用testInstanceMethod方法。
该程序输出如下:
The static method in Animal
The instance method in Cat
正如承诺的那样,被调用的隐藏静态方法是超类中的版本,而被调用的被重写实例方法则是子类中的版本。
接口方法
接口中的默认方法和抽象方法与实例方法一样被继承。然而,当类或接口的超类型提供多个具有相同签名的默认方法时,Java编译器会遵循继承规则来解决名称冲突。这些规则由以下两个原则驱动:
- 实例方法优先于接口默认方法。
考虑以下类和接口:
public class Horse {
public String identifyMyself() {
return "I am a horse.";
}
}
public interface Flyer {
default public String identifyMyself() {
return "I am able to fly.";
}
}
public interface Mythical {
default public String identifyMyself() {
return "I am a mythical creature.";
}
}
public class Pegasus extends Horse implements Flyer, Mythical {
public static void main(String... args) {
Pegasus myApp = new Pegasus();
IO.println(myApp.identifyMyself());
}
}
方法 Pegasus.identifyMyself() 返回字符串 I am a horse.。
- 已被其他候选方法覆盖的方法将被忽略。当超类型共享共同祖先时,可能会出现这种情况。
public interface Animal {
default public String identifyMyself() {
return "I am an animal.";
}
}
public interface EggLayer extends Animal {
default public String identifyMyself() {
return "I am able to lay eggs.";
}
}
public interface FireBreather extends Animal { }
public class Dragon implements EggLayer, FireBreather {
public static void main (String... args) {
Dragon myApp = new Dragon();
IO.println(myApp.identifyMyself());
}
}
方法 Dragon.identifyMyself() 返回字符串 I am able to lay eggs.。
若两个或多个独立定义的默认方法发生冲突,或默认方法与抽象方法冲突,则Java编译器将报出编译错误。必须显式重写超类方法。
考虑关于计算机控制的汽车现在能够飞行的例子。你有两个接口(OperateCar和FlyCar),它们为同一个方法(startEngine())提供了默认实现:
public interface OperateCar {
// ...
default public int startEngine(EncryptedKey key) {
// Implementation
}
}
public interface FlyCar {
// ...
default public int startEngine(EncryptedKey key) {
// Implementation
}
}
同时实现 OperateCar 和 FlyCar 接口的类必须重写 startEngine() 方法。你可以使用 super 关键字调用任何默认实现。
public class FlyingCar implements OperateCar, FlyCar {
// ...
public int startEngine(EncryptedKey key) {
FlyCar.super.startEngine(key);
OperateCar.super.startEngine(key);
}
}
super 之前的名称(在本例中为 FlyCar 或 OperateCar)必须指向直接实现接口,该接口为被调用方法定义或继承了默认实现。此类方法调用不仅限于区分多个实现接口中具有相同签名的默认方法。无论在类还是接口中,均可使用 super 关键字调用默认方法。
从类继承的实例方法可以覆盖抽象接口方法。请考虑以下接口和类:
public interface Mammal {
String identifyMyself();
}
public class Horse {
public String identifyMyself() {
return "I am a horse.";
}
}
public class Mustang extends Horse implements Mammal {
public static void main(String... args) {
Mustang myApp = new Mustang();
IO.println(myApp.identifyMyself());
}
}
Mustang.identifyMyself() 方法返回字符串 “I am a horse“。Mustang 类从 Horse 类继承了 identifyMyself() 方法,该方法覆盖了 Mammal 接口中同名的抽象方法。
注意:接口中的静态方法永远不会被继承。
修饰符
覆写方法的访问修饰符允许比被覆写方法更高的访问权限,但不能低于被覆写方法的访问权限。例如,父类中的 protected 实例方法在子类中可以被声明为 public,但不能声明为 private。
总结
下表总结了当您定义的方法与父类中的方法具有相同签名时会发生的情况。
| 父类实例方法 | 父类静态方法 | |
|---|---|---|
| 子类实例方法 | overrides | 编译错误 |
| 子类静态方法 | 编译错误 | hides |
注:在子类中,你可以重载从父类继承的方法。这些重载方法既不会隐藏也不会覆盖父类的实例方法——它们是子类特有的新方法。