一、什么是重写(Override)?(它解决了什么问题?)
想象一个家庭:
父亲(父类):有一个技能叫做饭(),他能做一道普通的“西红柿炒鸡蛋”。
儿子(子类):继承了父亲,也会做饭()。但是儿子去新东方进修过,他做的“西红柿炒鸡蛋”会颠勺、摆盘更精美、还加了独特的调料。
现在,当家里来客人说“让你家会做饭的人来做道菜”时:
如果让父亲出手,端上来的就是普通的西红柿炒鸡蛋。
如果让儿子出手,端上来的就是豪华升级版的西红柿炒鸡蛋。
儿子对“做饭”这个技能的全新实现,就叫做“重写”(Override)。
官方定义:
在面向对象编程中,当子类觉得从父类继承来的某个方法(技能)的实现细节不满足自己的需求时,它可以在自己内部重新编写这个方法的实现逻辑。这个过程就叫重写。
核心目的:
实现多态:同一个方法调用(如做饭()),作用于不同的对象(父亲或儿子),会产生不同的行为(普通菜或豪华菜)。这是面向对象编程最强大的特性之一。
扩展和定制:子类可以在保留父类方法名称和基本约定的前提下,提供自己独特的实现,从而扩展父类的功能。
二、重写的规则(想成功重写,必须遵守的“君子协定”)
重写不是乱写,必须遵守一系列规则,Java编译器会严格检查这些规则。规则的核心思想是:子类的新方法不能“欺骗”调用者。调用者以为自己在调用一个父类的方法,那么子类的方法就不能在基本约定上“耍花样”。
以下是必须遵守的规则:
1. 方法签名必须相同
方法名必须完全相同。
参数列表(参数的个数、类型、顺序)必须完全相同。
返回值类型:
在Java 5及以后,返回值类型可以相同,也可以是父类方法返回值类型的子类(这被称为“协变返回类型”)。但不能是毫不相关的类型。
通俗理解:父亲说“我做饭会返回一盘菜”。儿子可以说“我做饭会返回一盘五星级菜”(五星级菜是菜的子类),但不能说“我做饭会返回一个音乐CD”。
2. 访问权限不能更严格(但可以更宽松)
子类重写方法的访问权限(public, protected, 默认, private)不能比父类被重写方法的权限更严格。
通俗理解:父亲承诺他的方法对所有人都是public(公开的)。儿子重写后不能把它变成private(私有的),这样会违背父亲的承诺。但儿子可以保持为public,或者甚至更宽松(虽然通常无法更宽松,因为public已经是最宽松的了)。常见的规则是:
父类方法是 public,子类必须是 public。
父类方法是 protected,子类可以是 protected 或 public。
3. 异常抛出不能更宽泛
子类重写方法所声明抛出的异常(通常是checked exception)不能比父类方法声明的异常更宽泛(即不能是父类异常父类)。
通俗理解:父亲说“我做饭可能会抛出一个做饭烧糊了异常”。儿子可以说“我做饭可能会抛出一个翻炒太用力异常”(它是做饭烧糊了异常的子类),或者干脆不抛出异常。但不能说“我做饭可能会抛出一个所有厨房灾难异常”(它是做饭烧糊了异常的父类),这样调用者就无法做精确的异常处理了。
三、代码示例和规则验证
父类(Father.java)
public class Father {
// 父类有一个public方法,返回一个Car对象,声明抛出CookingException异常
public Car cook() throws CookingException {
System.out.println("父亲做了一盘普通的西红柿炒鸡蛋。");
return new Car(); // 返回一辆车(假设是报酬)
}
}
子类正确重写(Son.java)
public class Son extends Father {
// 【规则检查】:
// 1. 方法名、参数列表相同: (都是 cook(), 无参数)
// 2. 返回值类型是父类的子类(Porsche是Car的子类): (这叫协变返回类型)
// 3. 访问权限是public,和父类一样宽松:
// 4. 抛出的异常是父类异常的子类(BurntException是CookingException的子类):
@Override
public Porsche cook() throws BurntException {
System.out.println("儿子做了一盘米其林级别的西红柿炒鸡蛋!");
return new Porsche(); // 返回一辆保时捷(更好的报酬)
}
}
子类错误重写示例(会编译报错)
public class Son extends Father {
// 【错误示范】:
// 1. 返回值类型是毫不相关的MusicCD: 编译错误
// 2. 访问权限改为private,比父类的public更严格: 编译错误
// 3. 抛出的Exception是CookingException的父类,更宽泛: 编译错误
@Override
private MusicCD cook() throws Exception {
// ... ...
return new MusicCD();
}
}
四、如何标记重写?(@Override注解)
在Java中,强烈建议在重写的方法上加上@Override注解(Annotation)。
作用:
告诉编译器:“喂,编译器,我打算重写父类的一个方法,你帮我检查一下我的写法是否符合所有重写规则!” 如果不符合(比如你不小心把方法名写错了),编译器会立刻报错,帮你及时发现bug。
告诉读代码的人:“注意,这个方法是重写父类的,不是子类自己新定义的方法。”
一句话记重点:重写是子类把父类的同一个方法(同名同参)换了个更好的实现,但对外承诺(方法签名、返回值、异常等)不能变差,只能变好。
