这篇Java教程基于JDK1.8。教程中的示例和实践不会使用未来发行版中的优化建议。
5.2.2、方法重写与隐藏
实例方法
子类中与父类方法签名和返回值类型相同的方法将会重写父类的方法。
方法重写的能力允许类去扩展在行为上接近的父类,并根据需要修改其行为。重写的方法与被重写方法有相同的方法名,相同数量和类型的参数,相同的返回值类型。重写方法可以返回被重写方法返回值类型的子类型。该子类型称作协变返回类型。
重写方法时,需要使用@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 关键字前的名称必须是定义或者继承了默认方法的直接父类接口名称。这种调用形式能区分具有相同签名默认方法的多个实现。使用 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修饰。
将父类中的实例方法在子类中改为静态方法会造成编译错误。反之亦然。
总结
下面的表格展示了当你在子类中定义与父类方法签名相同的方法时所出现的各种情况:
父类实例方法 | 父类静态方法 | |
---|---|---|
子类实例方法 | Override | 编译错误 |
子类静态方法 | 编译错误 | Hides |
注意: 可以对从父类继承的方法进行重载。重载的方法不会重写也不会隐藏父类的实例方法—它们是独立的方法,子类独有的。