您的位置:首页 > 编程语言 > Java开发

Java 超类和子类(学习 Java 编程语言 042)

2021-01-27 14:25 731 查看

“is-a” 关系是继承的一个明显特征。

注释: 我们使用员工和经理的例子,不过这个例子要有所保留。在真实的世界里,员工也可能会成为经理,所以你建模时可能希望经理也是员工,而不是员工的一个子类。不过,在例子中,假设公司只有两类人:一些人永远是员工,另一些人一直是经理。

Employee 类:

import java.time.LocalDate;
import java.util.Objects;

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

@Override
public String toString() {
String str = "%s[name=%s, salary=%.2f, hireDay=%s]";
return String.format(str, getClass(), name, this.getSalary(), hireDay);
}
}

Manager 类:

public class Manager extends Employee {

private double bonus;

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

public void setBonus(double bonus) {
this.bonus = bonus;
}

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

1. 定义子类

继承 Employee 类来定义 Manager 类,使用关键字 extends 表示继承。

public class Manager extends Employee {
// added methods and fields
}

在 Java 中,所有的继承都是公共继承。

关键字 extends 表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)、基类(base class)或父类(parent class);新类称为子类(subclass)、派生类(derived class)或孩子类(child class)。超类和子类是 Java 程序员最常用的两个术语,而了解其他语言的程序员可能更加偏爱使用父类和孩子类,这可能很贴切地体现了“继承”。

尽管 Employee 类是一个超类,但并不是因为它优于子类或者拥有比子类更多的功能。实际上恰恰相反,子类比超类拥有的功能更多。例如,读过 Manager 类的源代码之后就会发现,Manager 类比超类 Employee 封装了更多的数据,拥有更多的功能。

注释: 前缀 “超”(super) 和 “子”(sub) 来源于计算机科学与数学理论中集合语言的术语。所有员工组成的集合包含所有经理组成的集合。可以这样说,员工集合是经理集合的超集,也可以说,经理集合是员工集合的子集。

在 Manager 类中,增加了一个用于存储奖金信息的字段,以及一个用于设置这个字段的新方法 :

public class Manager extends Employee
{
// 奖金
private double bonus;
...
public void setBonus(double bonus)
{
this.bonus = bonus;
}
}

这里定义的方法和字段并没有什么特别之处。如果有一个 Manager 对象,就可以使用 setBonus 方法。

Manager boss = ...;
boss.setBonus(5000);

由于 setBonus 方法不是在 Employee 类中定义的,所有属于 Employee 类的对象不能使用它。

然而,尽管在 Manager 类中没有显式地定义 getName 和 getHireDay 等方法,但是可以对 Manager 对象使用这些方法,这是因为 Manager 类自动地继承了超类 Employee 中的这些方法。

类似地,从超类中还继承了 name、salary 和 hireDay 这 3 个字段。这样一来,每个 Manager 对象就包含了 4 个字段:name、salary、hireDay 和 bonus。

通过扩招超类定义子类的时候,只需要指出子类与超类的不同之处。因此在设计类的时候,应当将最一般的方法放在超类中,而将更特殊的方法放在子类中,这种将通用功能抽取到超类的做法在面向对象设计中十分普遍。

2. 覆盖方法

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

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

应该如何实现这个方法呢?乍看起来似乎很简单,只要返回 salary 和 bonus 字段的总和就可以了:

public double getSalary() {
return salary + bonus; // 不能运行
}

不过,这样做是不行的。回想一下,只有 Employee 方法能直接访问 Employee 类的私有字段。这意味着,Manager 类的 getSalary 方法不能直接访问 salary 字段。如果 Manager 类的方法想要访问那些私有字段,就要像所有其他方法一样使用公共接口,在这里就是要使用 Employee 类中的公共方法 getSalary。

现在,再试一下。你需要调用 getSalary 方法而不是直接访问 salary 字段:

public double getSalary() {
double baseSalary = getSalary(); // 仍然不能运行
return baseSalary + bonus;
}

这段代码有问题。问题出现在调用 getSalary 的语句上,它只是在调用自身,这是因为 Manager 类也有一个 getSalary 方法,所以这条语句将会导致无限次调用自己,直到整个程序最终崩溃。

这里需要指出:我们希望调用超类 Employee 中的 getSalary 方法,而不是当前类的这个方法。为此,可以使用特殊的关键字 super 解决这个问题:

super.getSalary()

这个语句调用的是 Employee 类中的 getSalary 方法。下面是 Manager 类中 getSalary 方法的正确版本:

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

注释: 有些人认为 super 与 this 引用是类似的概念,实际上,这样比较并不太恰当。这是因为 super 不是一个对象的引用,例如,不能将值 super 赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。

在子类中可以增加字段、增加方法或覆盖超类的方法,不过,继承绝对不会删除父类任何字段或方法。

3. 子类构造器

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

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

由于 Manager 类的构造器不能访问 Employee 类的私有字段,所以必须通过一个构造器来初始化这些私有字段。可以利用特殊的 super 语法调用这个构造器。使用 super 调用构造器的语句必须是子类构造器的第一条语句。

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

this 关键字有两个含义:

  • 指示隐式参数的引用。
  • 调用该类的其他构造器。

super 关键字有两个含义:

  • 调用超类的方法。
  • 调用超类的构造器。

在调用构造器的时候,this 和 super 这两个关键字紧密相关。调用构造器的语句只能作为另一个构造器的第一条语句出现。构造器参数可以传递给当前类(this)的另一个构造器,也可以传递给超类(super)的构造器。

重新定义 Manager 对象的 getSalary 方法之后,奖金将会自动添加到经理的薪水中。
下面给出一个例子说明这个类的使用。我们要创建一个新经理,并设置他的奖金:

Manager boss = new Manager("Carl Cracker", 75000, 1987, 12, 15);
boss.setBonus(500000);

下面定义一个包含 3 个员工的数组:

Employee[] staff = new Employee[3];

在数组中混合填入经理和员工:

staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

输出每个人的信息:

for (Employee e : staff) {
System.out.println(e);
}

运行这条循环语句将会输出下列数据:

Carl Cracker 575000.0
Harry Hacker 50000.0
Tommy Tester 40000.0

这里的 staff[1] 和 staff[2] 仅输出了基本薪水,这是因为它们是 Employee 对象,而 staff[0] 是一个 Manager 对象,它的 getSalary 方法会将奖金和基本薪水相加。
需要提醒大家的是,以下调用

e.getSalay()

能够选出应该执行的正确 getSalary 方法。请注意,尽管这里将 e 声明为 Employee 类型,但实际上 e 即可以引用 Employee 类型的对象,也可以引用 Manager 类型的对象。

当 e 应用 Employee 对象时,e.getSalary() 调用的是 Employee 类中的 getSalary 方法;当 e 引用 Manager 对象时,e.getSalary() 调用的是 Manager 类中的 getSalary 方法。虚拟机知道 e 实际引用的对象类型,因此能够正确地调用相应的方法。

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

在 Java 中,动态绑定是默认的行为。如果不希望让一个方法虚拟的,可以将它标记为 final。

4. 继承层次

继承并不仅限于一个层次。例如,可以由 Manager 类派生 Executive 类。

由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy),在继承层次中,从某个特定的类到其祖先的路径称为该类的继承链(inheritance chain)

通常,一个祖先类可以拥有多个子孙继承链。例如,可以由 Employee 类派生出子类 Programmer 和 Secretary,它们与 Manager 类没有任何关系(它们彼此之间也没有任何关系)。必要的话,可以将这个过程一直延续下去。

Java 不支持多重继承,但提供了一些类似多重继承的功能——接口。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: