继承的起源
在面向对象编程中,可以通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类,这种方式称为继承(Inheritance)。
已有的类称为父类,而新类称为子类。父类也可以称为基类、超类,子类也可以称为派生类。这些术语可以互换,但是意思是相同的。
在面向对象编程中,为避免上面所出现的问题,我们需要根据一个简单的规则不断地测试我们的设计:一个对象“有(has a)”一个属性,一个对象“执行(does)”一个行为。
在面向对象编程中,当两个或多个类是不相同的,但是共享相同特征的时候,将共同的元素从类中拿出来,放到一个父类中。这些类继承父类,因此就继承了所有父类的特征,同时,每个子类中又保留了不同的特征。使用继承不仅可以重用已有的代码,从而避免代码重复,还可以创建一个更容易维护和修改代码的程序。
调用子类的时候,先在堆中划分父类的存贮空间,再在这个空间叠加子类的存储空间,构成了一个完整的地址空间。
继承是类与类的继承,不是对象与对象的继承。
使用is-a关系判断继承
“is a”关系是一个简单而强大的规则,用于判断父类和子类的继承关系是否正确。当使用继承时,我们必须能够说子类“ is a(是一个)” 父类。如果这个语句是真的,那么继承关系就是对的。
例如,“一个拿年薪的员工是一个员工”是真的。
Java类中继承的实现
在Java中,一个类使用关键字extends继承其它类。关键字extends出现在类声明时的类名后,extends后面跟着的是要继承的类的名称。
例如,下面的语句用于声明Salary类是Employee类的子类:
|
public class Salary extends Employee |
实例化子对象
Salary类是Employee类的子类)
单继承与多继承
某些OOP语言(例如C++)允许子类有多个父类。但是,在Java中这是不允许的。在Java中,一个类只能有一个父类。多继承在Java中是不允许的。Java语言的目标之一是创建一个易于使用和理解的面向对象编程语言。而多继承只能给编程语言带来混淆。
一个Java类只能有一个父类。但是这并不意味着一个类没有祖父、曾祖父等等。一个Java类可以有父类、父类也可以父类,依此类推。
所有的根类Object
Java语言API中包含了一个名为Object的特殊类,它是整个Java类层次中的根类。Object类在java.lang包中,是每个Java类的父类,要么是直接的父类,要么就是间接父类。
如果我们编写了一个类,并且没有显式地继承另一个类,那么编译器就会在我们的类声明中添加“extends Object”。如果我们编写的类继承了除Object以外的其它类,那么该类仍然是Object的子类,因为最终它的上代还是要继承Object类。
方法重写
子类可以重写从父类继承的方法,从而允许子类添加或者改变父类中方法的行为。这称为方法重写,是OOP的特征之一。
当子类重写父类的方法时,必须遵循如下的规则:
Object类中的toString()方法是声明为public的,因此,在Radio中的toString()方法必须声明为public。
Equals()方法
Object类有一个用于判断两个对象是否相等的equals()方法。我们编写的每一个类应该覆盖equals()方法,以让类的用户判断什么时候类的实例是相等的。
一个必须遵循的常用规则就是:如果两个对象是相等的,那么它们必须产生相同的哈希码。因此,如果一个类重写了equals()方法,通常也需要重写hashCode()方法。
equals()方法比较两个对象,测试二者是否相等。比较运算符“==”用于检测是否两个引用指向同一对象,这种比较方法是与equals()完全不同的。如果e1和e2指向不同的对象,那么不管e1是否与e2相等,表达式e1==e2的计算结果都是false。
super关键字
在前面,我们演示了子类如何通过方法重写,完全改变继承自父类的方法。有时,子类想要给继承的方法添加行为,但是并不是完全替换父类的方法。在这种情况下,可以使用关键字super来调用父类中重写的方法。
我们已经知道,每个对象都有一个对自身的引用,称为this引用。当在一个类中,引用类本身的成员变量或方法时,可以显式地使用this引用。类似地,一个类可以使用关键字super,来显式地引用从父类继承的成员变量或方法。我们可以把super看作是子对象对父对象的引用。
Final关键字
final类
有时候,我们希望一个类成为最终类,不能被其它类继承,也就是不能有子类。在这种情况下,我们就需要在声明类时,在关键字class前加上关键字final,让该类成为一个最终类。例如:
|
public final class Hourly extends Employee{ //类定义... } |
final方法
当子类重写父类的一个方法时,父类中被重写的方法实质上被隐藏了。在子类中,要调用父类中的方法,唯一的方法是显式地调用。如果我们编写的方法有中很重要的行为,我们不想子类重写,可以将该方法声明为最终方法。最终方法不能被子类重写。
实例化过程
子类对象是对父类的扩展。当子类被实例化时,父类对象需要先被构造。更特别地,父类的构造器必须在子类的构造器执行之前执行。此外,如果子类有祖父类,那么祖父对象需要先构造。这个过程一直向继承树继续传递。
当一个对象被实例化时,发生下列事件序列:
调用父类构造函数
关键字super用于调用父类的构造器。如果构造器没有显式地使用super关键字,那么编译器会自动添加一个无参数的super()来调用父类构造器。但是,如果父类没有无参数的构造器会发生什么呢?在这种情况下,调用空括号的super()不会通过编译。因此,子类构造器需要显式地调用父类构造器,并将恰当的实际参数传递给父类的构造器。
总结
本章的主要内容总结如下: