11.1 引言

  • 要点提示:面向对象编程支持从已经存在的类中定义新的类,这称为继承
  • 如本书前面章节所讨论的,面向过程范式的重点在于方法的设计,而面向对象范式将数据和方法结合在对象中。面向对象范式的软件设计着重于对象以及对象上的操作。面向对象的方法结合了面向过程范式的强大之处,并且进一步将数据和操作集成在对象中。
  • 继承是Java在软件重用方面一个重要且功能强大的特征。假设要定义一个类,对圆、矩形和三角形建模。这些类由很多共同的特性。设计这些类来避免冗余并使系统易于理解和易于维护的最好方式是什么?答案就是继承

11.2 父类和子类

  • 要点提示: 继承使得你可以定义一个通用的类(即父类),之后继承该类为一个更特定的类(即子类)。
  • 使用类来对同一类型的对象建模。不同的类可能会有一些共同的特征和行为,可以在一个通用类中表达这些共同之处,并被其他类所共享。可以定义特定的类继承自通用类。这些特定的类继承通用类中的特征和方法。
  • 考虑一下几何对象。假设要设计类来对像圆和矩形这样的几何对象建模。几何对象由许多共同的属性和行为。它们可以是用某种颜色画出来的,可以填充或者不填充。可以用一个通用类GeometricObject来建模所有的几何对象。这个类包括属性color 和filled ,以及适用于这些属性的获取方法和设置方法。假设该类还包括dataCreated 属性以及getDateCreated( ) 和toString( )方法。toString( ) 方法返回该对象的字符串表示。由于圆是一个特殊的几何对象,所以它和其他几何对象共享共同的属性和方法。因此,通过继承GeometricObject类来定义Circle 类是有意义的。同理,Rectangle也可以定义为GeometricObject的特殊类型。图11-1显示了这些类之间的关系,指向通用类的三角箭头用来表达涉及的两个类之间的继承关系。
  • 在Java术语中,如果类C1继承自另一个类C2,那么就将C1称为子类(subclass),将C2称为超类(superclass)。超类也称为父类(parent class)或基类(base class),子类又称为继承类(extended class)或派生类(derived class)。子类从它的父亲中继承可访问的数据域和方法,还可以添加新的数据域和方法。因此,Circle 和Rectangle 都是GeometricObject的子类,GeometricObject是Circle 和Rectangle 的父类。一个类定义了一个类型。由子类定义的类型称为子类型(subtype),由父类定义的类型称为父类型(supertype)。因此,可以说Circle 是GeometricObject的子类型,而GeometricObject是CIrcle 的父类型。
  • 子类和它的父类形成了”是一种”(is - a) 关系。Circle 对象是通用的GeometricObject的一种特殊类型。Circle类继承了GeometricObject类中所有可访问的数据域和方法。除此之外,它还有一个新的数据域radius,以及相关的获取方法和设置方法。Circle 类还包括getArea( )、getPerimeter( ) 和 getDiameter( ) 以返回圆的面积、周长和直径。
  • Rectangle 类从GeometricObject类继承所有可访问的数据域和方法。此外,它还有width 和 height 数据域以及相关的获取方法和设置方法。它还包括getArea( ) 和 getPerimer( )返回矩形的面积和周长。注意,你可能在几何中使用术语宽度和长度来描述矩形的边。计算机科学中的通用术语为宽度和高度,其中宽度指水平长度,高度指垂直长度。
  • GeometricObject类、Circle 类和Rectangle 类分别在程序清单11-1、程序清单11-2 和程序清单11-3中给出

程序清单 11-1 GeometricObject.java

  1. public class GeometricObject {
  2. private String color = "white";
  3. private boolean filled;
  4. private java.util.Date dateCreated;
  5. /**
  6. * Construct a default geometric object
  7. */
  8. public GeometricObject(){
  9. dateCreated = new java.util.Date();
  10. }
  11. /**
  12. * Construct a geometric object with the specified color and filled value
  13. */
  14. public GeometricObject(String color, boolean filled){
  15. dateCreated = new java.util.Date();
  16. this.color = color;
  17. this.filled = filled;
  18. }
  19. /**
  20. * Return color
  21. */
  22. public String getColor(){
  23. return color;
  24. }
  25. /**
  26. * Set a new color
  27. */
  28. public void setColor(String color){
  29. this.color = color;
  30. }
  31. /**
  32. * Return filled. Since filled is boolean
  33. * its getter method is named ifFilled
  34. */
  35. public boolean isFilled() {
  36. return filled;
  37. }
  38. /**
  39. * Set a new filled
  40. */
  41. public void setFilled(boolean filled) {
  42. this.filled = filled;
  43. }
  44. /**
  45. * Get dataCreated
  46. */
  47. public java.util.Date getDateCreated() {
  48. return dateCreated;
  49. }
  50. /**
  51. * Return a string representation of this object
  52. */
  53. public String toString(){
  54. return "created on " + dateCreated + "\ncolor: " + color +
  55. " and filled: " + filled;
  56. }
  57. }

程序清单 11-2 Circle.java

  1. public class Circle extends GeometricObject {
  2. private double radius;
  3. public Circle() {
  4. }
  5. public Circle(double radius) {
  6. this.radius = radius;
  7. }
  8. public Circle(double radius, String color, boolean filled) {
  9. this.radius = radius;
  10. setColor(color);
  11. setFilled(filled);
  12. }
  13. /**
  14. * Return radius
  15. */
  16. public double getRadius() {
  17. return radius;
  18. }
  19. /**
  20. * Set a new radius
  21. */
  22. public void setRadius(double radius) {
  23. this.radius = radius;
  24. }
  25. /**
  26. * Return area
  27. */
  28. public double getArea() {
  29. return radius * radius * Math.PI;
  30. }
  31. /**
  32. * Return diameter
  33. */
  34. public double getDiameter() {
  35. return 2 * radius;
  36. }
  37. /**
  38. * Return perimeter
  39. */
  40. public double getPerimeter() {
  41. return 2 * radius * Math.PI;
  42. }
  43. /**
  44. * Print the circle info
  45. */
  46. public void printCircle() {
  47. System.out.println("The circle is created " + getDateCreated()
  48. + " and the radius is " + radius);
  49. }
  50. }
  • 关键字extends(第1行)告诉编译器,Circle 类继承自GeometricObject类,这样,它就继承了getColor、setColor、isFilled、setFilled 和 toString 方法。
  • 重载的钩爪方法Circle(double radius, String color, boolean filled) 是通过调用setColor 和 setFilled 方法来设置color 和filled 属性的(第14和15行)。这两个公共方法是在父类GeometricObject中定义的,并在Circle 中继承,因此可以在Circle 类中使用它们。
  • 你可能会试图在构造方法中直接使用数据域color 和filled ,如下所示:
    1. public Circle(double radius, String color, boolean filled){
    2. this.radius = radius;
    3. this.color = color; //Illegal
    4. this.filled = filled; //Illegal
    5. }
  • 这是错误的,因为GeometricObject类中的私有数据域color 和 filled是不能被除了GeometricObject类本身之外的其他任何类访问的。唯一读取和改变color 与 filled 的方法就是通过它们的获取方法和设置方法。
  • Rectangle 类(程序清单11-3)使用下面的语法继承GeometricObject类(程序清单11-1);
  • 关键字extends(第1行)告诉编译器Rectangle 类继承自GeometricObject类,也就是继承了getColor、setColor、isFilled、setFilled 和toString 方法

程序清单 11-3 Rectangle.java

  1. public class Rectangle extends GeometricObject {
  2. private double width;
  3. private double height;
  4. public Rectangle() {
  5. }
  6. public Rectangle(double width, double height) {
  7. this.width = width;
  8. this.height = height;
  9. }
  10. public Rectangle(double width, double height, String color, boolean filled) {
  11. this.width = width;
  12. this.height = height;
  13. setColor(color);
  14. setFilled(filled);
  15. }
  16. /**
  17. * Return width
  18. */
  19. public double getWidth() {
  20. return width;
  21. }
  22. /**
  23. * Set a new width
  24. */
  25. public void setWidth(double width) {
  26. this.width = width;
  27. }
  28. /**
  29. * Return height
  30. */
  31. public double getHeight() {
  32. return height;
  33. }
  34. /**
  35. * Set a new height
  36. */
  37. public void setHeight(double height) {
  38. this.height = height;
  39. }
  40. /**
  41. * Return area
  42. */
  43. public double getArea() {
  44. return width * height;
  45. }
  46. /**
  47. * Return perimeter
  48. */
  49. public double getPerimeter() {
  50. return 2 * (width + height);
  51. }
  52. }
  • 程序清单11-4中的代码创建了Circle 和Rectangle 的对象,并调用这些对象上的方法。toString( )方法继承自GeometricObject 类,并且从Circle 对象(第4行)和 Rectangle 对象(第11行)调用。

程序清单 11-4 TestCircleRectangle.java

  1. public class TestCircleRectangle {
  2. public static void main(String[] args) {
  3. Circle circle = new Circle();
  4. System.out.println("A circle " + circle.toString());
  5. System.out.println("The color is " + circle.getColor());
  6. System.out.println("The radius is " + circle.getRadius());
  7. System.out.println("The area is " + circle.getArea());
  8. System.out.println("The diameter is " + circle.getDiameter());
  9. Rectangle rectangle = new Rectangle(2,4);
  10. System.out.println("\nA rectangle " + rectangle.toString());
  11. System.out.println("The area is " + rectangle.getArea());
  12. System.out.println("The perimeter is " + rectangle.getPerimeter());
  13. }
  14. }
  • 下面是关于继承应该注意的几个关键点:
    • 和习惯说法不同,子类并不是父类的一个子集。实际上,一个子类通常比它的父类包含更多的信息和方法。
    • 父类中的私有数据域在该类之外是不可访问的。因此,不能再子类中直接使用。但是,如果父类中定义了公共的访问器 / 修改器,那么可以通过这些公共的访问器 / 修改器来访问 / 修改它们。
    • 不是所有的 “是一种”(is - a)关系都该用继承来建模。例如:正方形是一种矩形,但是不应该定义一个Square 类来继承自Rectangle 类,因为width 和 height 属性并不适合于正方形。应该定义一个继承自GeometricObject类的Square 类,并为正方形的边定义一个side 属性。
    • 继承是用来为 “是一种” 关系建模的。不要仅仅为了重用方法这个原因而盲目地继承一个类。例如:尽管Person 类 和Tree类可以共享类似height 和weight 这样的通用特性,但是从Person 类继承出Tree类是毫无意义的。一个父类和它的子类之间必须存在”是一种” 关系。
    • 某些程序设计语言是运行从几个类派生出一个子类的。这种能力称为多重继承(multiple inheritance)。但是在 Java 中是不允许多重继承的。一个Java类只可能直接继承自一个父类。这种限制称为单一继承(single inheritance)。如果使用extends 关键字来定义一个子类,它只允许有一个父类。然而,多重继承是可以通过接口来实现的,这部分内容将在13.5 节中介绍。

11.3 使用super关键字

  • 要点提示:关键字super 指代父类,可以用于调用父类中的普通方法和构造方法
  • 子类继承它的父类中所有可访问的数据域和方法。它能继承构造方法吗?父类的构造方法能够从子类调用吗?本节就来解决这些问题以及衍生出来的问题。
  • 9.14节中介绍了关键字this 的作用,它是对调用对象的引用。关键字super是指这个super 关键字所在的类的父类。关键字super 可以用于两种途径:
    • 调用父类的构造方法
    • 调用父类的普通方法

11.3.1 调用父类方法

  • 构造方法用于构建一个类的实例。不同于属性和普通方法,父类的构造方法不会被子类继承。它们只能使用关键字super从子类的构造方法中调用。
  • 调用父类构造方法的语法是:
    1. super() 或者 super(arguments);
  • 语句super( ) 调用父类的无参构造方法,而语句super(argument) 调用与arguments 匹配的父类的构造方法。语句super( ) 或super(arguments) 必须出现在子类构造方法的第一行,这是显式调用父类构造方法的唯一方式。例如,在程序清单11-2中的第11~16行的构造方法可以用下面的代码替换:
    1. public Circle(double radius, String color, boolean filled){
    2. super(color, filled);
    3. this.radius = radius;
    4. }
  • 要调用父类的构造方法就必须使用关键字super,而且这个调用必须是构造方法的第一条语句。在子类中调用父类构造方法的名字会引起一个语法错误。

11.3.2 构造方法链

  • 构造方法可以调用重载的构造方法或父类的构造方法。如果它们都没有被显式地调用,编译器就会自动地将super( ) 作为构造方法的第一条语句。例如:
  • 在任何情况下,构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法。当构造一个子类的对象时,子类的构造方法会在完成自己的任务之前,首先调用它的父类的构造方法。如果父类继承自其他类,那么父类的构造方法又会在完成自己的任务之前,调用它自己的父类的构造方法。这个过程持续到沿着这个继承层次结构的最后一个构造方法被调用位置。这就构造方法链(construct chaining)。
  • 思考下面的代码:

    1. public class Faculty extends Employee{
    2. public static void main(String[] args){
    3. new Faculty();
    4. }
    5. public Faculty(){
    6. System.out.println("(4) Performs Faculty's tasks");
    7. }
    8. }
    9. class Employee extends Person{
    10. public Employee(){
    11. this("(2) Invoke Employee's overloaded constructor");
    12. System.out.println("(3) Performs Employee's tasks");
    13. }
    14. public Employee(String s){
    15. System.out.println(s);
    16. }
    17. }
    18. class Person{
    19. public Person(){
    20. System.out.println("(1) Performs Person's tasks");
    21. }
    22. }

程序清单 11-5 PolymorphismDemo.java

  1. public class PolymorphismDemo {
  2. /**
  3. * Main method
  4. */
  5. public static void main(String[] args) {
  6. //Display circle and rectangle properties
  7. displayObject(new Circle(1, "red", false));
  8. }
  9. /**
  10. * Display geometric object properties
  11. */
  12. private static void displayObject(GeometricObject object) {
  13. System.out.println("Created on " + object.getDateCreated() +
  14. ".Color is " + object.getColor());
  15. }
  16. }

程序清单 11-6 DynamicBindingDemo.java

  1. public class DynamicBindingDemo {
  2. public static void main(String[] args) {
  3. m(new GeometricObject());
  4. m(new Student());
  5. m(new Person());
  6. m(new Object());
  7. }
  8. public static void m(Object x) {
  9. System.out.println(x.toString());
  10. }
  11. }
  12. class GraduateStudent extends Student {
  13. }
  14. class Student extends Person {
  15. @Override
  16. public String toString() {
  17. return "Student";
  18. }
  19. }
  20. class Person extends Object {
  21. @Override
  22. public String toString() {
  23. return "Person";
  24. }
  25. }

程序清单 11-7 CastingDemo.java

  1. public class CastingDemo {
  2. /**
  3. * Main method
  4. */
  5. public static void main(String[] args) {
  6. //Create and initialize two objects
  7. Object object1 = new Circle(1);
  8. Object object2 = new Rectangle(1, 1);
  9. //Display circle and rectangle
  10. displayObject(object1);
  11. displayObject(object2);
  12. }
  13. /**
  14. * A method for display an object
  15. */
  16. public static void displayObject(Object object) {
  17. if (object instanceof Circle) {
  18. System.out.println("The circle area is " +
  19. ((Circle) object).getArea());
  20. System.out.println("The circle diameter is " +
  21. ((Circle) object).getDiameter());
  22. } else if (object instanceof Rectangle) {
  23. System.out.println("The rectangle area is " +
  24. ((Rectangle) object).getArea());
  25. }
  26. }
  27. }

程序清单 11-8 TestArrayList.java

  1. import java.util.ArrayList;
  2. public class TestArrayList {
  3. public static void main(String[] args) {
  4. //Create a list to store cities
  5. ArrayList<String> cityList = new ArrayList<>();
  6. //Add some cities in the list
  7. cityList.add("London");
  8. //cityList now contains [London]
  9. cityList.add("Denver");
  10. //cityList now contains [London,Denver]
  11. cityList.add("Paris");
  12. //cityList now contains [London,Denver,Paris]
  13. cityList.add("Miami");
  14. //cityList now contains [London,Denver,Paris,Miami]
  15. cityList.add("Seoul");
  16. //cityList now contains [London,Denver,Paris,Miami,Seoul]
  17. cityList.add("Tokyo");
  18. //cityList now contains [London,Denver,Paris,Miami,Seoul,Tokyo]
  19. System.out.println("List size? " + cityList.size());
  20. System.out.println("Is Miami in the list? " + cityList.indexOf("Denver"));
  21. //Print false
  22. System.out.println("Is the list empty? " + cityList.isEmpty());
  23. //Insert a new city at index2
  24. cityList.add(2, "Xian");
  25. //Contains [London,Denver,Xian,Paris,Miami,Seoul,Tokyo]
  26. //Remove a city from the list
  27. cityList.remove("Miami");
  28. //Contains [London,Denver,Xian,Paris,Seoul,Tokyo]
  29. //Remove a city from the list
  30. cityList.remove(1);
  31. //Contains [London,Xian,Paris,Seoul,Tokyo]
  32. //Display the contents in the list
  33. System.out.println(cityList.toString());
  34. //Display the contents in the list in reverse order
  35. for (int i = cityList.size() - 1; i >= 0; i--) {
  36. System.out.print(cityList.get(i) + " ");
  37. }
  38. System.out.println();
  39. //Create a list to store two circles
  40. ArrayList<Circle> list = new ArrayList<>();
  41. //Add two circles
  42. list.add(new Circle(2));
  43. list.add(new Circle(3));
  44. //Display the area of the first circle in the list
  45. System.out.println("The area of the circle? " + list.get(0).getArea());
  46. }
  47. }

程序清单 11-9 DistinctNumbers.java

  1. import java.util.ArrayList;
  2. import java.util.Scanner;
  3. public class DistinctNumbers {
  4. public static void main(String[] args) {
  5. ArrayList<Integer> list = new ArrayList<>();
  6. Scanner input = new Scanner(System.in);
  7. System.out.print("Enter integers (input ends with 0): ");
  8. int value;
  9. do {
  10. //Read a value from the input
  11. value = input.nextInt();
  12. if (!list.contains(value) && value != 0) {
  13. //Add the value if it is not in the list
  14. list.add(value);
  15. }
  16. } while (value != 0);
  17. //Display the distinct numbers
  18. for (int i = 0; i < list.size(); i++) {
  19. System.out.print(list.get(i) + " ");
  20. }
  21. }
  22. }

程序清单 11-10 DistinctNumbers.java

  1. import java.util.ArrayList;
  2. public class MyStack {
  3. private ArrayList<Object> list = new ArrayList<>();
  4. public boolean isEmpty() {
  5. return list.isEmpty();
  6. }
  7. public int getSize() {
  8. return list.size();
  9. }
  10. public Object peek() {
  11. return list.get(getSize() - 1);
  12. }
  13. public Object pop() {
  14. Object o = list.get(getSize() - 1);
  15. list.remove(getSize() - 1);
  16. return o;
  17. }
  18. public void push(Object o) {
  19. list.add(o);
  20. }
  21. @Override
  22. public String toString() {
  23. return "stack: " + list.toString();
  24. }
  25. }