Java学习笔记(九)——继承与多态

继承

继承(inheritance)是面向对象程序设计的一个基本概念。利用继承,我们可以基于一个已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域,以满足新的需求。这是 Java 程序设计中的一项核心技术。

举一个不太恰当,但是足以说明继承这个概念的例子:雇员(Employee)类和经理(Manager)类。从理论上讲,在 Manager 与 Employee 之间存在明显的“is-a”(是)关系,每个经理都是一名雇员:“is-a”关系是继承的一个明显特征。

下面我们来搞懂几个概念:已存在的类称为超类(superclass)、基类(base class)或父类(parent class);新类称为派生类(derived class) 或 子类(subclass / child class)。

先给出 Employee 的代码:

package inheritance;

import java.time.*;

public class Employee {

    private String name;
    private double salary;
    private LocalDate hireDay;

    public Employee(String name, double salary, int year, int month, int day) {
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    public void raiseSalary(double byPercent) {
        double raise = salary*byPercent / 100;
        salary += raise;
    }

}

接下来,我们定义子类:

下面是由继承 Employee 类来定义 Manager 类的格式,关键字 extends 表示继承。

public class Manager extends Employee{
    添加方法和域
}

注意:在 Java 中,所有的继承都是公有继承,而没有 C++ 中的私有继承和保护继承

我们把 Employee 代码先贴出来,然后再解释:

package inheritance;

public class Manager extends Employee {

    private double bonus;

    /**
     * @param name the employee's name
     * @param salary the salary
     * @param year the hire year
     * @param month the hire month
     * @param day the dire day
     */

    public Manager(String name, double salary, int year, int month, int day) {
        super(name, salary, year, month, day);
        bonus = 0;
    }

    public double getSalary() {
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }

    public void setBonus(double b) {
        bonus = b;
    }

}

子类的对象可以使用超类的方法,超类的对象不能使用子类的方法。

当超类中的方法对子类不适用时,具体的说,Manager 类中的 getSalary 方法应该返回薪水和奖金的总和。为此,需要提供一个新方法来覆盖(override)超类中的这个方法:

public class Manager extends Employee{
    ...
    public double getSalary(){
    ...
    }
    ...
}

应该如何实现这个方法呢?好像很简单,我们来试试:

public class getSalary(){
    return salary+bonus; // won't work
}

然而,这个方法是不行的。因为 Manager 类的 getSalary 方法不能直接地访问超类的私有域也就是说,尽管每个 Manager 对象都拥有一个名为 salary 的域,但在 Manager 类的 getSalary 方法并不能够直接访问 salary 域。只有 Employee 类的方法才能够访问私有部分。如果 Manager 类的方法一定要访问私有域,那么必须借助于公有接口,Employee 类中的共有方法 getSalary 正是这样一个接口。现在。我们再试一次:

public double getSalary() {
    double baseSalary = getSalary(); // still won't work
    return baseSalary + bonus;
}

上面这段代码仍然不能运行。问题出在调用的 getSalary 的语句上,这是因为 Manager 类也有一个 getSalary 方法(也就是正在实现的这个方法)。所以这条语句将会无限地调用自己,直到程序崩溃。

所以我们需要指出:我们希望调用超类的 getSalary 方法,而不是当前的这个方法。为此,我们使用特定的关键词 super 来解决问题:

public double getSalary() {
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }

我们可以在 Manager 类的代码中看到这段代码:

public Manager(String name, double salary, int year, int month, int day) {
    super(name, salary, year, month, day);
    bonus = 0;
}

这里的关键字 super 与前面的 super 有不同的含义。语句

super(name, salary, year, month, day);

是“调用超类 Employee 中含有 n、s、year、month 和 day 参数的构造器”的简写形式。

由于 Manager 类的构造器不能访问 Employee 类的私有域,所以必须利用 Employee 类的构造器对这部分私有域进行初始化,我们可以通过 super 实现对超类构造器的调用。使用 super 调用构造器的语句必须是子类构造器的第一条语句。

如果子类的构造器没有显示地调用超类的构造器,则将自动的调用默认超类。如果超类没有不带参数的构造器,并且在子类的构造器中又没有显示地调用超类的其他构造器,则 Java 编译器将报告错误。

注释: 关键字 this 有两个用途:一是引用隐式参数,二是调用该类其他的构造器。同样,super 关键字也有两个用途:一是调用超类的方法,二是调用超类的构造器。

我们来贴测试代码:

package inheritance;

/**
 * This program demonstrates inheritance
 * @version 1.8 2018-2-5
 * @author ShenXueYan
 *
 */

public class ManagerTest {

    public static void main(String[] args) {
        //construct a Manager object
        Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
        boss.setBonus(5000);

        Employee[] staff = new Employee[3];

        //fill the staff array with Manager and Employee object
        staff[0] = boss;
        staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
        staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

        //print out information about all Employee object
        for(Employee e : staff) {
            System.out.println("name=" + e.getName() + ", salary=" + e.getSalary());
        }

    }

}

一个对象变量可以指示多种实际类型的现象被称为 多态(polymorphism)。运行时能够自动地选择调用哪个方法的现象称为 动态绑定(dynamic binding)。

在 Java 中,不需要将方法声明为虚拟方法。动态绑定是默认的处理方式。如果不希望一个方法具有虚拟特征,可以将它标记为 final 。

多态

置换法则:程序中出现超类对象的任何地方都可以用子类对象置换。

例如,可以将一个子类对象赋值给超类变量。

Employee e;
Employee[] staff = new Employee[3];
staff[0] = boss;

在 Java 程序设计语言中,对象变量是多态的。一个 Employee 变量既可以引用一个 Employee 类对象,也可以引用一个 Employee 类的任何一个子类的对象。

当然,把超类的引用赋值给子类变量是不可以的。

有关 阻止继承的 final 类和方法 的讨论该开始了,下面我们简要概述:

有时候,我们可能不希望人们利用某个类定义子类。不允许扩展的类被称为 final 类。如果在定义类的时候使用 final 修饰符就表明这个类是 final 类。格式如下:

public final class Executive extends Manager{
    ...
}

类中的特定方法也可以被声明为 final。如果这样做,子类就不能覆盖这个方法。值得一提的是:final 类中的所有方法都自动成为 final 方法,但不包括域。


你可能感兴趣的:(编程语言-Java)