在Java开发中,重写(Override)是面向对象编程(OOP)中的一个重要概念。它允许子类提供父类方法的具体实现,从而改变或扩展父类的行为。重写是实现多态性的重要手段,使得程序在运行时能够根据对象的实际类型来调用相应的方法。
在实际应用中,重写父类方法的场景非常广泛。例如,假设我们正在开发一个图形处理软件,其中有多种形状(如圆形、矩形、三角形等)。我们可以定义一个父类Shape
,然后为每种形状创建子类。在这些子类中,我们可以重写Shape
类中的draw
方法,以实现不同形状的绘制逻辑。
1. 什么是重写?
重写是指在子类中重新定义父类的方法。重写的方法必须与父类的方法具有相同的方法名、参数列表和返回类型。重写的目的是为了提供子类特有的实现。
2. 重写的规则
-
方法名相同:子类方法的名称必须与父类方法相同。
-
参数列表相同:子类方法的参数列表必须与父类方法的参数列表相同。
-
返回类型相同:子类方法的返回类型必须与父类方法的返回类型相同,或者是其子类(协变返回类型)。
-
访问修饰符:子类方法的访问修饰符不能比父类方法的修饰符更严格。例如,如果父类方法是
public
,那么子类方法不能是protected
或private
。 -
抛出异常:子类方法可以抛出父类方法抛出的异常,也可以抛出更具体的异常,但不能抛出更广泛的异常。
示例代码
下面我们通过一个简单的示例来演示如何重写父类方法。
示例 1:基本的重写
// 定义一个父类 Shape
class Shape {
// 父类方法
public void draw() {
System.out.println("Drawing a shape");
}
}
// 定义一个子类 Circle 继承自 Shape
class Circle extends Shape {
// 重写父类的 draw 方法
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
// 定义一个子类 Rectangle 继承自 Shape
class Rectangle extends Shape {
// 重写父类的 draw 方法
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
// 主类
public class Main {
public static void main(String[] args) {
// 创建 Shape 类型的引用指向 Circle 对象
Shape shape1 = new Circle();
shape1.draw(); // 输出: Drawing a circle
// 创建 Shape 类型的引用指向 Rectangle 对象
Shape shape2 = new Rectangle();
shape2.draw(); // 输出: Drawing a rectangle
}
}
代码解释
-
**父类
Shape
**:定义了一个draw
方法,表示绘制形状。 -
子类
Circle
和 **Rectangle
**:分别重写了draw
方法,提供了各自的实现。 -
**主类
Main
**:创建了Shape
类型的引用,但指向不同的子类对象。通过这种方式,我们实现了多态性。
示例 2:重写与多态
在实际开发中,重写通常与多态结合使用。我们可以使用父类类型的引用来调用子类的方法,具体调用哪个方法在运行时决定。
// 父类
class Animal {
public void sound() {
System.out.println("Animal makes a sound");
}
}
// 子类 Dog
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
// 子类 Cat
class Cat extends Animal {
@Override
public void sound() {
System.out.println("Cat meows");
}
}
// 主类
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.sound(); // 输出: Dog barks
myCat.sound(); // 输出: Cat meows
}
}
代码解释
-
**父类
Animal
**:定义了一个sound
方法,表示动物发出的声音。 -
子类
Dog
和 **Cat
**:分别重写了sound
方法,提供了不同的实现。 -
**主类
Main
**:通过Animal
类型的引用调用Dog
和Cat
的sound
方法,展示了多态性。
示例 3:重写中的访问修饰符
重写方法时,访问修饰符的选择也很重要。以下示例展示了如何在重写时使用不同的访问修饰符。
// 父类
class Vehicle {
// 父类方法,使用 public 修饰符
public void start() {
System.out.println("Vehicle is starting");
}
}
// 子类 Car
class Car extends Vehicle {
// 重写父类方法,仍然使用 public 修饰符
@Override
public void start() {
System.out.println("Car is starting");
}
}
// 子类 Bike
class Bike extends Vehicle {
// 重写父类方法,使用 protected 修饰符(错误示例)
// @Override
// protected void start() { // 这将导致编译错误
// System.out.println("Bike is starting");
// }
}
// 主类
public class Main {
public static void main(String[] args) {
Vehicle myCar = new Car();
myCar.start(); // 输出: Car is starting
}
}
代码解释
-
**父类
Vehicle
**:定义了一个start
方法,使用public
修饰符。 -
**子类
Car
**:重写了start
方法,仍然使用public
修饰符。 -
**子类
Bike
**:尝试使用protected
修饰符重写start
方法,这会导致编译错误,因为protected
比public
更严格。
生活中的比喻
重写可以用生活中的许多场景来比喻。例如,想象一个父亲和他的孩子。父亲有一个技能,比如“游泳”。孩子可以继承这个技能,但他可以选择在游泳的方式上做出改变,比如“花样游泳”或“潜水”。在这个比喻中,父亲的游泳方式相当于父类的方法,而孩子的游泳方式则是重写的方法。