在面向对象编程中,继承是实现代码复用的一种重要机制。当子类需要改变或扩展父类的某个方法时,可以使用覆写(Override)这一特性。覆写发生在子类定义一个与父类完全相同签名(即方法名和参数列表相同)的方法时。这意味着,当通过子类对象调用该方法时,将执行子类中的方法,而不是父类中的方法。
以下是一个简单的例子,说明如何在Java中使用覆写:
首先,我们有一个Person
类,其中定义了一个run
方法:
class Person {
public void run() {
System.out.println("Person is running.");
}
}
接着,我们有一个Student
类,它继承了Person
类并覆写了run
方法:
class Student extends Person {
@Override
public void run() {
System.out.println("Student is running.");
}
}
在上面的Student
类中,@Override
注解告诉编译器我们正在覆写一个父类中的方法。如果Student
类的run
方法签名与Person
类的run
方法签名不一致,编译器会报错,因为这不是覆写,而是重载(Overload)或者是一个全新的方法。
注意,覆写的方法必须与被覆写的方法有相同的返回类型(或者是协变的返回类型,这在Java 5及之后的版本中是允许的,指的是子类方法的返回类型是父类方法返回类型的子类或相同类型)。如果返回类型不同,即使方法名和参数列表相同,也不是覆写。
以下是一个错误使用覆写的例子:
class Student extends Person {
// 编译错误!这不是覆写,因为参数列表不同
@Override
public void run(String s) {
System.out.println("Student is running with message: " + s);
}
// 编译错误!这不是覆写,因为返回类型不同
@Override
public int anotherRun() {
return 0; // 假设这里有一个整数值返回
}
}
在上面的代码中,run(String s)
方法不是覆写,因为它接受一个字符串参数,这与Person
类中的run
方法签名不同。anotherRun
方法也不是覆写,因为它不仅方法名不同,而且返回类型也不同。
在Java中,@Override
注解并不是必须的,但它是一个好的编程实践,因为它可以帮助编译器在编译时检查方法签名是否确实与父类中的方法匹配。如果方法签名不匹配,编译器会报错,从而避免潜在的运行时错误。
接下来,我们讨论一种常见情况:当子类覆写了父类的方法时,通过父类引用指向子类对象来调用该方法,会发生什么。
考虑以下代码示例:
public class Main {
public static void main(String[] args) {
Person p = new Student(); // 父类引用指向子类对象
p.run(); // 调用run方法
}
}
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
在这个例子中,p
是一个Person
类型的引用,但实际上它指向的是一个Student
对象。当我们调用p.run()
时,尽管p
是Person
类型的引用,但由于它实际指向的是Student
对象,因此会调用Student
类中的run
方法。
运行上述代码,输出结果将是:
Student.run
这展示了Java中多态性的一个关键特性:方法的调用是基于对象的实际类型,而不是引用变量的声明类型。这意味着,即使引用变量是父类类型,只要它实际指向的是子类对象,并且子类覆写了父类的方法,那么调用该方法时就会执行子类中的版本。
多态
多态是面向对象编程中的一个重要概念,它允许在运行时根据对象的实际类型来决定调用哪个具体的方法。这种特性使得程序更加灵活和可扩展。下面通过一个具体的例子来说明多态的作用和优势。
首先,我们定义了一个基础的Income
类,它代表一种收入,并提供了计算税款的getTax()
方法:
class Income {
protected double income;
public Income(double income) {
this.income = income;
}
public double getTax() {
return income * 0.1; // 假设税率为10%
}
}
接着,我们根据不同类型的收入定义了两个子类:Salary
和StateCouncilSpecialAllowance
。Salary
类代表工资收入,并重写了getTax()
方法以实现特定的税收优惠政策:
class Salary extends Income {
public Salary(double income) {
super(income);
}
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2; // 超过5000部分的税率为20%
}
}
StateCouncilSpecialAllowance
类代表国务院特殊津贴,其getTax()
方法被重写为总是返回0,表示这种收入免税:
class StateCouncilSpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}
@Override
public double getTax() {
return 0; // 国务院特殊津贴免税
}
}
现在,我们编写一个totalTax()
方法来计算多种收入的总税款。这个方法接受一个Income
类型的可变参数,并通过循环调用每个收入对象的getTax()
方法来累加税款:
public double totalTax(Income... incomes) {
double total = 0;
for (Income income : incomes) {
total += income.getTax();
}
return total;
}
在main
方法中,我们创建了一个包含普通收入、工资收入和国务院特殊津贴的收入数组,并调用totalTax()
方法来计算总税款:
public class Main {
public static void main(String[] args) {
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes)); // 输出总税款
}
}
通过这个例子,我们可以看到多态的强大之处。totalTax()
方法只与Income
类型打交道,而不需要关心传入的具体子类类型。当有新的收入类型时,只需继承Income
类并重写getTax()
方法,而无需修改原有的totalTax()
方法。这样的设计使得代码易于维护和扩展,充分体现了面向对象编程的开闭原则(对扩展开放,对修改封闭)。