定义子类
可以用如下继承Employee类来定义Manager类,这里使用关键字extend表示继承。
public class Manager extends Employee{
*added methods an fields*
}
关键字extends表示正在构建的新类派生于一个已存在的类。这个已存在的类称为超类或者父类;新类称之为子类或者派生类。
尽管Employee类是一个超类,但并不是因为他优于子类或者拥有比子类更多的功能。实践上恰恰相反反而是子类比超类拥有的功能更多。
在Manager类中增加了一个存储信息的字段,以及一个用于设置这个字段的新方法:
public class Manager extend Employee{
private double bonus;
...
public void setBonus(double bonus){
this.bonus=bonus;
}
}
这里定义的方法和字段并没有什么特别之处。如果有一个manager对象,就可以使用setBonus方法。
Manager boss=...;
boss.setBonus=bonus;
当然由于setBonus不是在Employee上定义的,所以属于Employee类的对象不能使用它。
然而尽管Manager类中没有显式的定义getName和getHireDay等方法,但是可以对Manager对象使用这些方法,这是因为Manager类自动继承了超类Employee类的这些方法。
类似的,从超类继承的name、salary、hireDay和bonus。
通过拓展超类定义子类的时候,只需要指出子类和超类的不同之处。因此在设计类的时候,应该将最一般的方法设置到超类中,从而将更特殊的方法放在子类中,这种将通用功能抽取到超类的做法在面向对象程序设计中十分普遍。
覆盖方法
超类中有一些方法对子类不一定适用。具体来说,对Manager类中的getSalary方法应该返回薪水和奖金的总和。为此,需要提供一个新的方法来覆盖超类中的这个方法:
public class Manager extends Employee{
...
public double getSalary(){
...
}
'''
}
应该如何实现这个方法呢?乍一看非常的简单,只需要返回salary和bonus的总和就行了:
public double getSalary(){
return salary+bonus; //无法执行
}
不过那样是不行的。回想一下,只有Employee方法能够直接访问Employee的私有变量。这意味着,Manager类无法调用那些私有变量,除非像所有其他方法一样使用公共接口,在这里就是要使用Employee类中的公共方法getSalary。
现在再试一下。我们需要调用getSalary方法而不是直接访问salary字段:
public duoble getSalary{
double baseSalary = getSalary();//依然是无法执行
return baseSalary+bonus;
}
上面这段代码会继续报错。问题出现在getSalary上,它只是在调用自身,这是因为Manager类也有一个getSalary方法(就是我们现在在实现的这个方法),所以这条语句将无限次调用自己,直到程序崩溃。
所以我们就需要关键字super来调用超类Employee的getSalary方法而不是子类Manager类的方法:
super.getSalary
下面是使用后正确调用getSalary的版本
public duoble getSalary{
double baseSalary = super.getSalary();
return baseSalary+bonus;
}
正像前面所看到的那样,在子类可以增加字段,增加方法或者覆盖超类的方法,不过,继承绝不会删除任何字段或者方法。
子类构造器
在例子的最后,我们来提供一个构造器
public Manager(String name,double salary,int year,int month,int day){
super (name,salary,year,month,day);
bonus=0;
}
这里的关键字super具有不同的意义。语句super (name,salary,year,month,day);
是“调用超类中带有n,s,year,month,day参数的构造器”的简写形式。
由于Manager类的构造器不能访问Employee类的私有字段,所以必须通过一个构造器来初始化这些私有字段。可以利用特殊的super语法调用这个构造器。使用super调用构造器的语句必须是子类构造器的第一条语句。
如果子类的构造器没有显式的调用超类的构造器,将自动调用超类的无参数构造器。如果超类没有的无参数构造器,子类的构造器也没有显式的调用超类的构造器,Java编译器就会报告一个错误。
重新定义Manager对象的getSalary方法之后,奖金就会自动地添加到经理的薪水中。
下面给出一个例子来说明这个类的使用。我们要创建一个新经理,并设置他的奖金:
Manager boss=new Manager("Corlone",80000,2001,08,04)
boss.setBonus(5000);
下面定义一个包含三个员工的数组:
var staff=new Employee[3];
在数组中混合填入经理和员工;
staff[0]=boss;
staff[1] = new Employee("Harry", 50000, 1990, 10, 1);
staff[2] = new Employee("Tom", 40000, 1997, 3, 15);
输出每个人的薪水:
for(Employee e:staff)
System.out.println("name:" + e.getName() + ",salary:" + e.getSalary());
运行这条循环语句将会输出以下数据:
name:Corlone,salary: 85000.0
name:Harry,salary: 50000.0
name:Tom ,salary:40000.0
这里的staff[1]和staff[2]只输出了基本薪水,这是因为它们是Employee对象,而staff[0]是一个Manager对象,它的getSalary方法会将奖金与基本薪水相加。 需要提醒的是,以下调用 e.getSalary()
能够选出执行的正确getSalary方法;当e声明为Employee类型,但实际上e既能够引用Employee类型的对象,也可以引用Manager类型的对象。
当e引用Employee的对象时,e.getSalary调用的是Employee类的getSalary()方法;当e引用Manager的对象时,e.getSalary调用的是Manager类的getSalary()方法。虚拟机知道e实际上引用的对象类型,因此能够正确的调用相应的方法。
一个对象变量(例如e)可以指示多种实际类型的现象叫做多态。在运行时能够自动地选择适当的方法,称之为动态绑定。
程序清单1展示Employee对象(见程序清单2)与Manager对象(见程序清单3)在薪水上计算的区别。
程序运行清单1
public class Manager
{
public static void main(String[] args)
{
Manager boss=new Manager("Corlone",80000,2001,08,04)
boss.setBonus(5000);
var staff=new Employee[3];
staff[0]=boss;
staff[1] = new Employee("Harry", 50000, 1990, 10, 1);
staff[2] = new Employee("Tom", 40000, 1997, 3, 15);
//打印内容
for(Employee e:staff)
System.out.println("name:" + e.getName() + ",salary:" + e.getSalary());
}
}
程序运行清单2
import java.time.*;
public class Employee {
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String n,double s,int year,int month,int day){
name = n;
salary = s;
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;
}
}
程序运行清单3
public class Manager extend Employee
{
private double bonus;
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 bonus)
{
this.bonus=bonus;
}
}