Java的面向对象 -- 多态

在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为 覆写Override)。

例如,在 Person 类中,我们定义了 run() 方法:

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}

在子类 Student 中,覆写这个 run() 方法:

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

OverrideOverload 不同的是,如果方法签名如果不同,就是 OverloadOverload
方法是一个新方法;如果方法签名相同,并且返回值也相同,就是 Override

注意:方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。
在Java程序中,出现这种情况,编译器会报错。

class Person {
    public void run() {}
}

class Student extends Person {
    // 不是Override,因为参数不同:
    public void run(String s) {}
    // 不是Override,因为返回值不同:
    public int run() {}
}

加上 @Override 可以让编译器帮助检查是否进行了正确的覆写。希望进行覆写,但是不小心写错了方法签名,编译器会报错。

public class Main {
    public static void main(String[] args) {
    }
}

class Person {
    public void run() {}
}

public class Student extends Person {
    @Override 			// Compile error!
    public void run(String s) {}
}

但是 @Override 不是必需的。

在上一节中,我们已经知道,引用变量的声明类型可能与其实际类型不符,例如:

Person p = new Student();

现在,我们考虑一种情况,如果子类覆写了父类的方法:

public class Main {
    public static void main(String[] args) {
        Person p = new Student();
        p.run(); 		// 应该打印Person.run还是Student.run?
    }
}

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

那么,一个实际类型为 Student ,引用类型为 Person 的变量,调用其 run() 方法,调用的是 Person 还是 Studentrun() 方法?

运行一下上面的代码就可以知道,实际上调用的方法是 Studentrun() 方法。

因此可得出结论:Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。

这个非常重要的特性在面向对象编程中称之为 多态

多态

多态 是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。例如:

Person p = new Student();
p.run(); 			// 无法确定运行时究竟调用哪个run()方法

上面的代码一看就明白,肯定调用的是 Studentrun() 方法啊。

但是,假设我们编写这样一个方法:

public void runTwice(Person p) {
    p.run();
    p.run();
}

它传入的参数类型是 Person ,我们是无法知道传入的参数实际类型究竟是 Person ,还是 Student ,还是 Person 的其他子类,因此,也无法确定调用的是不是 Person 类定义的 run() 方法。

所以,多态 的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。这种不确定性的方法调用,究竟有什么作用?

我们举个例子:

假设我们定义一种收入,需要给它报税,那么先定义一个 Income 类:

class Income {
    protected double income;
    public double getTax() {
        return income * 0.1; // 税率10%
    }
}

对于工资收入,可以减去一个基数,那么我们可以从 Income 派生出 SalaryIncome ,并覆写 getTax()

class Salary extends Income {
    @Override
    public double getTax() {
        if (income <= 5000) {
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

如果你享受国务院特殊津贴,那么按照规定,可以全部免税:

class StateCouncilSpecialAllowance extends Income {
    @Override
    public double getTax() {
        return 0;
    }
}

现在,我们要编写一个报税的财务软件,对于一个人的所有收入进行报税,可以这么写:

public double totalTax(Income... incomes) {
    double total = 0;
    for (Income income: incomes) {
        total = total + income.getTax();
    }
    return total;
}

来试一下:

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));
    }

    public static double totalTax(Income... incomes) {
        double total = 0;
        for (Income income: incomes) {
            total = total + income.getTax();
        }
        return total;
    }
}

class Income {
    protected double income;

    public Income(double income) {
        this.income = income;
    }

    public double getTax() {
        return income * 0.1; // 税率10%
    }
}

class Salary extends Income {
    public Salary(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        if (income <= 5000) {
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

class StateCouncilSpecialAllowance extends Income {
    public StateCouncilSpecialAllowance(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        return 0;
    }
}

观察 totalTax() 方法:利用 多态totalTax() 方法只需要和 Income 打交道,它完全不需要知道 SalaryStateCouncilSpecialAllowance 的存在,就可以正确计算出总的税。如果我们要新增一种稿费收入,只需要从Income 派生,然后正确覆写 getTax() 方法就可以。把新的类型传入 totalTax() ,不需要修改任何代码。

可见,多态 具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

super调用

在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过 super 来调用。例如:

class Person {
    protected String name;
    public String hello() {
        return "Hello, " + name;
    }
}

Student extends Person {
    @Override
    public String hello() {
        // 调用父类的hello()方法:
        return super.hello() + "!";
    }
}
final关键字

继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为 final 。用 final 修饰的方法不能被 Override

class Person {
    protected String name;
    public final String hello() {
        return "Hello, " + name;
    }
}

Student extends Person {
    // compile error: 不允许覆写
    @Override
    public String hello() {
    }
}

如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为 final 。用 final 修饰的类不能被继承:

final class Person {
    protected String name;
}

// compile error: 不允许继承自Person
Student extends Person {
}

对于一个类的实例字段,同样可以用 final 修饰。用 final 修饰的字段在初始化后不能被修改。例如:

class Person {
    public final String name = "Unamed";
}

final 字段重新赋值会报错:

Person p = new Person();
p.name = "New Name"; 		// compile error!

可以在构造方法中初始化 final 字段:

class Person {
    public final String name;
    public Person(String name) {
        this.name = name;
    }
}

这种方法更为常用,因为可以保证实例一旦创建,其 final 字段就不可修改。

小结

子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为;

Java的方法调用总是作用于运行期对象的实际类型,这种行为称为 多态

final 修饰符有多种作用:

final 修饰的方法可以阻止被覆写;

final 修饰的 class 可以阻止被继承;

final 修饰的 field 必须在创建对象时初始化,随后不可修改。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值