覆盖和隐藏方法
实例方法
子类中具有相同签名(名称,加上参数的数量和类型)和返回类型的实例方法会覆盖超类的该方法。
子类覆盖方法的能力允许一个类从行为“足够接近”的超类继承,然后根据需要修改行为。覆盖方法具有与被覆盖方法相同的名称、参数数量和类型以及返回类型。覆盖方法还可以返回被覆盖方法返回类型的子类型。这种子类型称为协变返回类型。
覆盖方法时,您可能希望使用 @Override
注解,该注解指示编译器您打算覆盖超类中的方法。如果由于某种原因,编译器检测到该方法在某个超类中不存在,那么它将生成错误。有关 @Override
的更多信息,请参阅 注解 部分。
静态方法
如果子类定义了一个与超类中的静态方法具有相同签名的静态方法,那么子类中的方法将隐藏超类中的方法。
隐藏静态方法和覆盖实例方法之间的区别具有重要的意义
被覆盖的实例方法的版本是子类中的版本。
被隐藏的静态方法的版本取决于它是从超类还是子类调用。
考虑一个包含两个类的示例。第一个是
Animal
,它包含一个实例方法和一个静态方法
public class Animal {
public static void testClassMethod() {
System.out.println("The static method in Animal");
}
public void testInstanceMethod() {
System.out.println("The instance method in Animal");
}
}
第二个类是 Animal
的子类,称为 Cat
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The static method in Cat");
}
public void testInstanceMethod() {
System.out.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();
System.out.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();
System.out.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();
System.out.println(myApp.identifyMyself());
}
}
Mustang.identifyMyself()
方法返回字符串 I am a horse
。Mustang
类从 Horse
类继承 identifyMyself()
方法,该方法覆盖了接口 Mammal
中同名抽象方法。
注意:接口中的静态方法永远不会被继承。
修饰符
覆盖方法的访问说明符可以允许比被覆盖方法更多的访问权限,但不能更少。例如,超类中的 protected
实例方法可以在子类中变为 public
,但不能变为 private
。
如果您尝试将超类中的实例方法更改为子类中的静态方法,反之亦然,您将得到编译时错误。
总结
下表总结了当您定义一个与超类中的方法具有相同签名的方法时会发生什么。
超类实例方法 | 超类静态方法 | |
---|---|---|
子类实例方法 | 覆盖 | 生成编译时错误 |
子类静态方法 | 生成编译时错误 | 隐藏 |
注意:在子类中,您可以重载从超类继承的方法。这种重载方法既不隐藏也不覆盖超类实例方法——它们是子类独有的新方法。
上次更新: 2021 年 9 月 14 日