本教程针对JDK 8编写。本页面描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及后续版本中更新的语言功能的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能和已删除或不推荐选项的信息,请参阅JDK发布说明。
子类中的实例方法与父类中具有相同签名(名称、参数的数量和类型)和返回类型的实例方法覆盖了父类的方法。
子类重写方法的能力允许类从一个行为足够"接近"的父类继承,然后根据需要修改行为。重写的方法与它所覆盖的方法具有相同的名称、参数的数量和类型,以及返回类型。重写的方法也可以返回被覆盖方法返回类型的子类型。这个子类型被称为协变返回类型。
当重写一个方法时,你可能想要使用@Override注解,告诉编译器你打算重写父类的方法。如果由于某种原因,编译器检测到该方法在任一父类中不存在,那么它将生成一个错误。有关@Override的更多信息,请参阅Annotations。
如果子类定义了一个与父类中的静态方法具有相同签名的静态方法,那么子类中的方法隐藏了父类中的方法。
隐藏静态方法和重写实例方法之间的区别具有重要的含义:
考虑一个包含两个类的示例。第一个类是Animal,其中包含一个实例方法和一个静态方法:
public class Animal {
public static void testClassMethod() {
System.out.println("Animal类中的静态方法");
}
public void testInstanceMethod() {
System.out.println("Animal类中的实例方法");
}
}
第二个类是Cat,它是Animal的子类:
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("Cat类中的静态方法");
}
public void testInstanceMethod() {
System.out.println("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()。
这个程序的输出如下:
动物类中的静态方法 猫类中的实例方法
正如承诺的那样,调用的隐藏静态方法是超类中的方法,调用的覆盖的实例方法是子类中的方法。
默认方法和抽象方法在接口中的继承和实例方法一样。然而,当一个类或接口的超类型提供了多个具有相同签名的默认方法时,Java编译器遵循继承规则来解决命名冲突。这些规则遵循以下两个原则:
实例方法优先于接口默认方法。
考虑以下类和接口:
public class Horse {
public String identifyMyself() {
return "我是一匹马。";
}
}
public interface Flyer {
default public String identifyMyself() {
return "我能飞。";
}
}
public interface Mythical {
default public String identifyMyself() {
return "我是一个神话生物。";
}
}
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返回字符串我是一匹马。
已经被其他候选方法覆盖的方法会被忽略。这种情况可能发生在超类型共享一个共同的祖先时。
考虑以下接口和类:
public interface Animal {
default public String identifyMyself() {
return "我是一个动物。";
}
}
public interface EggLayer extends Animal {
default public String identifyMyself() {
return "我能下蛋。";
}
}
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返回字符串我能下蛋。
如果两个或更多独立定义的默认方法冲突,或者默认方法与抽象方法冲突,则Java编译器会产生编译器错误。你必须显式地重写超类型的方法。
考虑一下关于现在能飞的计算机控制汽车的例子。您有两个接口(OperateCar和FlyCar),它们为相同的方法(startEngine)提供默认实现:
public interface OperateCar {
// ...
default public int startEngine(EncryptedKey key) {
// 实现
}
}
public interface FlyCar {
// ...
default public int startEngine(EncryptedKey key) {
// 实现
}
}
一个实现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 "我是一匹马。";
}
}
public class Mustang extends Horse implements Mammal {
public static void main(String... args) {
Mustang myApp = new Mustang();
System.out.println(myApp.identifyMyself());
}
}
方法Mustang.identifyMyself返回字符串我是一匹马。类Mustang从类Horse继承了方法identifyMyself,它覆盖了接口Mammal中同名的抽象方法。
注意:接口中的静态方法不会被继承。
覆盖方法的访问修饰符可以比被覆盖的方法允许更多的访问权限,但不能更少。例如,超类中的受保护实例方法可以在子类中被设置为公共的,但不能是私有的。
如果您尝试将超类中的实例方法更改为子类中的静态方法,或者反过来,将会得到编译时错误。
下表总结了在定义与超类中的方法具有相同签名的方法时会发生的情况。
| 超类实例方法 | 超类静态方法 | |
|---|---|---|
| 子类实例方法 | 重写 | 生成编译时错误 |
| 子类静态方法 | 生成编译时错误 | 隐藏 |