当前位置:首页分享Java从入门到放弃(二十五):多态(上)

Java从入门到放弃(二十五):多态(上)

在面向对象编程中,继承是实现代码复用的一种重要机制。当子类需要改变或扩展父类的某个方法时,可以使用覆写(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()时,尽管pPerson类型的引用,但由于它实际指向的是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%
    }
}

接着,我们根据不同类型的收入定义了两个子类:SalaryStateCouncilSpecialAllowanceSalary类代表工资收入,并重写了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()方法。这样的设计使得代码易于维护和扩展,充分体现了面向对象编程的开闭原则(对扩展开放,对修改封闭)。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧