1. 方法重写
在现实生活中,常常会遇到“子代”表现得与众不同的情况。比如,所有动物都会发出声音,但猫会“喵”,狗会“汪”,而程序员会“哎,又是 bug!”在编程中,这通过方法重写(override)来实现。
方法重写是指子类为父类已经声明的方法提供自己的实现。也就是说,用更具体的实现“替换”默认行为。
类比:如果把父类想象成红菜汤的招牌配方,那么方法重写就是奶奶往里面加了她的秘密配料。红菜汤还是红菜汤,但各家的味道不同。
要重写方法,必须在子类中声明一个签名完全相同(名称、参数、返回类型)的方法,与父类的方法一致。
示例:动物及其叫声
class Animal {
void makeSound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
// 重写 makeSound() 方法
void makeSound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
// 重写 makeSound() 方法
void makeSound() {
System.out.println("Meow!");
}
}
现在,如果创建对象 Dog 并调用 makeSound(),你会听到 "Woof!",而不是 "Some generic animal sound"。
代码演示
public class Main {
public static void main(String[] args) {
Animal generic = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();
generic.makeSound(); // Some generic animal sound
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
}
}
重要:如果子类没有具有相同签名的方法,将使用父类的方法。
2. 注解 @Override:用途与用法
在 Java 中,重写的方法通常会使用 @Override 注解标注。这不仅仅是代码的“装饰”,而是一个有用的工具:
- 编译器会检查你是否确实在重写父类方法。如果你在名称、参数类型或返回类型上有误——编译器会报错。
- 提升代码可读性。其他开发者一眼就能看到:“哦,这个方法重写了父类的方法”。
带有 @Override 的示例
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
如果不小心把方法写成 void makeSound()(拼写错误!),并且标了 @Override,编译器会报错:"Method does not override or implement a method from a supertype"。
现代实践:使用 @Override 是良好习惯与行业标准。即使编译器不强制,也请始终加上这个注解——这会让你和同事更省心。
3. 调用被重写的方法是如何工作的
当你在子类对象上调用方法时,即使变量声明为父类类型,也会使用子类中的实现。
示例:多态的实际表现
Animal animal = new Dog();
animal.makeSound(); // "Woof!",而不是 "Some generic animal sound"
这里变量的静态类型是 Animal,但实际保存的是 Dog 对象。Java 会“知道”需要调用 Dog 中被重写的方法。这就是多态(更多细节将在后续讲解)。
4. 重写的规则与限制
方法签名
- 名称、参数类型与顺序必须与父类方法一致。
- 返回类型必须一致,或是其协变(是父类返回类型的子类型)。例如,若父类返回 Animal,子类返回 Dog 是允许的。
访问修饰符
- 不能把可见性变得比父类更严格。
- 如果父类方法是 public,则重写的方法也必须是 public。
- 如果父类方法是 protected,则可重写为 protected 或 public。
若尝试相反操作,编译器会提示:"Cannot reduce the visibility of the inherited method"。
异常
- 重写的方法不能抛出父类方法声明中没有的新的受检异常(checked exception)。
- 可以抛出更少的异常,或抛出其子类型。
static、final、private
- 不能重写被声明为 static 或 final 的方法,以及私有(private)方法。
- static 的情况是“隐藏”(hiding),而非重写。
- final 方法完全不能被重写,Java 会保护这些方法。
- private 方法在子类中不可见,无法重写(只能新声明一个同名方法)。
构造器
构造器不会被继承,也不能被重写。每个类都有自己的构造器。
5. 继续完善教学应用“动物园”
该把理论用于实践了!让我们继续完善我们的“动物园”应用。
步骤 1:基类 Animal
public class Animal {
public void makeSound() {
System.out.println("Some generic animal sound");
}
public void sleep() {
System.out.println("Zzz...");
}
}
步骤 2:子类 Dog 和 Cat
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
// 仅 Dog 拥有的附加方法
public void fetch() {
System.out.println("Dog brings the stick!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
// 仅 Cat 拥有的附加方法
public void scratch() {
System.out.println("Cat scratches the sofa!");
}
}
步骤 3:使用重写
public class ZooTest {
public static void main(String[] args) {
Animal generic = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
generic.makeSound(); // Some generic animal sound
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
// dog.fetch(); // 错误!Animal 类型的变量不知道 fetch()
// cat.scratch(); // 同理
// 但如果显式指定类型:
if (dog instanceof Dog) {
((Dog) dog).fetch(); // Dog brings the stick!
}
if (cat instanceof Cat) {
((Cat) cat).scratch(); // Cat scratches the sofa!
}
}
}
评论:
方法 makeSound() 具有多态性——会调用对象实际类中的版本。而特有方法(fetch、scratch)只有通过显式类型转换才能访问——这有助于理解继承与重写的工作方式。
6. 返回类型示例(协变)
有时我们希望重写的方法返回更“窄”的类型。例如:
class Animal {
Animal getFriend() {
return new Animal();
}
}
class Dog extends Animal {
@Override
Dog getFriend() { // 返回类型为 Dog,是 Animal 的子类型
return new Dog();
}
}
这称为返回类型的协变,在 Java 中是被允许的(自 Java 5 起)。
7. 如果不使用 @Override 会怎样?
如果你在方法名或参数上不小心写错,而又没有使用 @Override,Java 不会报错。结果你不会重写,而是创建了一个新方法,预期的行为并不会改变。
错误示例
class Dog extends Animal {
// 拼写错误:makeSound 而不是 makeSound
void makeSound() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.makeSound(); // 输出 "Some generic animal sound"
}
}
如果使用了 @Override,编译器会报错:"Method does not override or implement a method from a supertype"。
8. 方法重写中的常见错误
错误 1:没有使用 @Override 注解。
没有它,很容易在方法名或参数上出错。结果方法不会被重写,程序行为也不会如预期。
错误 2:尝试收窄访问修饰符。
如果父类方法是 public,而你写成 protected 或 private——会得到编译错误。
错误 3:签名不匹配。
如果参数哪怕只有类型不同——那就不是重写,而是重载(overloading)。
错误 4:试图重写 final 或 static 方法。
Java 不允许这样做:final 阻止重写,而 static 方法本就不可重写(只能隐藏)。
错误 5:将返回类型改为不兼容的类型。
只能返回父类返回类型的子类型(协变),而不能是完全无关的类型。
GO TO FULL VERSION