继承(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 方法,但不包括域。