重写和隐藏方法
实例方法
子类中的实例方法的签名(名称,加上其参数的数量和类型)和返回类型与超类中的实例方法相同,将覆盖超类的方法。
子类覆盖方法的能力允许类从行为“足够接近”的超类继承,然后根据需要修改行为,重写方法与它重写的方法具有相同的名称、数量和参数类型,以及返回类型。重写方法还可以返回由被重写方法返回的类型的子类型,此子类型称为协变返回类型。
覆盖方法时,你可能希望使用@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
。
如果尝试将超类中的实例方法更改为子类中的静态方法,则会出现编译时错误,反之亦然。
总结
下表总结了在定义具有与超类中的方法相同的签名的方法时发生的情况。
超类实例方法 | 超类静态方法 | |
---|---|---|
子类实例方法 | 覆盖 | 生成编译时错误 |
子类静态方法 | 生成编译时错误 | 隐藏 |
注意:在子类中,你可以重载从超类继承的方法,这样的重载方法既不隐藏也不覆盖超类实例方法 — 它们是新方法,对于子类是唯一的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。