11. 继承和多态
面向对象的编程允许你从已经存在的类中定义新的类,这称为继承。
11.1 父类和子类
继承使得你可以定义一个通用的类(即父类),之后扩充该类为一个更加特定的类(即子类)。
使用类来对同一类型的对象建模。不同的类也可能会有一些共同的特征和行为,这些共同的特征和行为都统一放在一个类中,它是可以被其他类所共享的。你可以定义特定的类继承自通用类。这些特定的类继承通用类中的特征和方法。
考虑一下几何对象。假设要设计类建模像圆和矩形这样的几何对象。几何对象有许多共同的属性和行为。它们可以是用某种颜色画出来的、填充的或者不填充的。这样,一个通用类 CeometricObject 可以用来建模所有的几何对象。这个类包括属性 color 和 filled , 以及适用于这些属性的 get 和 set 方法。假设该类还包括 dateCreated 属性以及 getDateCreated( ) 和 toString( ) 方法。toString( ) 方法返回代表该对象的字符串。由于圆是一个特殊类型的几何对象,所以它和其他几何对象共享共同的属性和方法。因此,通过继承自 CeometricObject 类来定义 Circle 类是合理的。同理,Rectangle 也可以定义为 GeometricObject 的子类。下图显示了这些类之间的关系,指向父类的三角箭头用来表相关的两个类之间的继承关系。
在 Java 术语中,如果类 C1 扩展自另一个类 C2, 那么就将 C1 称为次类( subclass ), 将C2 称为超类(superclass )。超类也称为父类 ( parent class) 或基类 ( base class), 次类又称为子类 ( child class)、扩展类 ( extended class) 或派生类(derived class )。子类从它的父类中继承可访问的数据域和方法,还可以添加新数据域和新方法。 Circle 类继承了 CeometricObject 类所有可以访问的数据域和方法。除此之外,它还有一个新的数据域 radius, 以 及 与 radius 相关的 get 和 set 方法。它还包括 getArea( ) 、 getPerimeter( ) 和 getDiameter( ) 方法以返回圆的面积、周长和直径。Rectangle 类从 CeometricObject 类继承所有可访问的数据域和方法。此外,它还有 width 和 height 数据域,以及和它们相关的 get 和 set 方法。它还包括 getArea( ) 和 getPerimeter( ) 方法返回矩形的面积和周长。
下面是关于继承应该注意的几个关键点:
- 和传统的理解不同,子类并不是父类的一个子集。实际上,一个子类通常比它的父类包含更多的信息和方法。
- 父类中的私有数据域在该类之外是不可访问的。因此,不能在子类中直接使用。但是,如果父类中定义了公共的访问器 / 修改器,那么可以通过这些公共的访问器 / 修改器来访问和修改它们。
- 不是所有的 “是一种”(is - a) 关系都该用继承来建模。例如:正方形是一种矩形,但是不应该定义一个 Square 类来扩展 Rectangle 类,因为 width 和 height 属性并不适合于正方形。应该定义一个继承自 CeometricObject 类的 Square 类,并为正方形的边定义一个 side 属性。
- 继承是用来为 “是一种” 关系( is - a) 建模的。不要仅仅为了重用方法这个原因而盲目地扩展一个类。例如:尽管 Person 类和 Tree 类可以共享类似高度和重量这样的通用特性,但是从 Person 类扩展出 Tree 类是毫无意义的。一个父类和它的子类之间必须存在 “是一种”(is - a) 关系。
- 某些程序设计语言是允许从几个类派生出一个子类的。这种能力称为多重继承 ( multiple inheritance)。但是在 Java 中是不允许多重继承的。一个 Java 类只可能直接继承自一个父类。这种限制称为单一继承( single inheritance)。如果使用 extends 关键字来定义一个子类,它只允许有一个父类。然而,多重继承是可以通过接口来实现的。
11.2 使用 super 关键字
关键字 super 指代父类,可以用于调用父类中的普通方法和构造方法。
关键字 super 可以用于两种途径:
1 ) 调用父类的构造方法。
2 ) 调用父类的方法。
11.2.1 调用父类的构造方法
构造方法用于构建一个类的实例。不同于属性和普通方法,父类的构造方法不会被子类继承。它们只能使用关键字 super 从子类的构造方法中调用。
调用父类构造方法的语法是:
super()或者super(parameters);
语句 super( ) 调用父类的无参构造方法,而语句 super(arguments) 调用与参数匹配的父类的构造方法。语句 super( ) 和 super (arguments) 必须出现在子类构造方法的第一行,这是显式调用父类构造方法的唯一方式。
警告:要调用父类构造方法就必须使用关键字 super, 而且这个调用必须是构造方法的第一条语句。在子类中调用父类构造方法的名字会引起一个语法错误。
11.2.2 构造方法链
构造方法可以调用重载的构造方法或父类的构造方法。如果它们都没有被显式地调用,编译器就会自动地将 super( ) 作为构造方法的第一条语句。例如:
在任何情况下,构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法。当构造一个子类的对象时,子类构造方法会在完成自己的任务之前,首先调用它的父类的构造方法。如果父类继承自其他类,那么父类构造方法又会在完成自己的任务之前,调用它自己的父类的构造方法。这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止。 这就是构造方法链(constructor chaining)。
警告:如果要设计一个可以被继承的类,最好提供一个无参构造方法以避免程序设计错误。
思考下面的代码: