我们把 Circle 类声明为 shapes 包的一部分。假设我们计划实现多个表示形状的类:Rectangle、Square、Ellipse、Triangle 等。我们可以在这些表示形状的类中定义两个基本方法:area()circumference()。那么,为了能方便处理由形状组成的数组,这些表示形状的类最好有个共同的超类 Shape。这样组织类层次结构的话,每个形状对象,不管具体表示的是什么形状,都能赋予类型为 Shape 的变量、字段或数组元素。我们想在 Shape 类中封装所有形状共用的功能(例如,area()circumference() 方法)。但是,通用的 Shape 类不表示任何类型的形状,所以不能为这些方法定义有用的实现。Java 使用抽象方法解决这种问题。

    Java 允许使用 abstract 修饰符声明方法,此时只定义方法但不实现方法。abstract 修饰的方法没有主体,只有一个签名和一个分号。以下是 abstract 方法和这些方法所在的abstract 类相关的规则。

    • 只要类中有一个 abstract 方法,那么这个类本身就自动成为 abstract 类,而且必须声明为 abstract 类,否则会导致编译出错。
    • abstract 类无法实例化。
    • abstract 类的子类必须覆盖超类的每个 abstract 方法并且把这些方法全部实现(即提供方法主体),才能实例化。这种类一般叫作具体子类(concrete subclass),目的是强调它不是抽象类。
    • 如果 abstract 类的子类没有实现继承的所有 abstract 方法,那么这个子类还是抽象类,而且必须使用 abstract 声明。
    • 使用 staticprivatefinal 声明的方法不能是抽象方法,因为这三种方法在子类中不能覆盖。类似地,final 类中不能有任何 abstract 方法。
    • 就算类中没有 abstract 方法,这个类也能声明为 abstract。使用这种方式声明的abstract 类表明实现的不完整,要交给子类实现。这种类不能实例化。

    下面通过一个示例说明这些规则的运作方式。 如果定义 Shape 类时把 area()circumference() 声明为 abstract 方法,那么 Shape 的子类必须实现这两个方法才能实例化。也就是说,每个 Shape 对象都要确保实现了这两个方法。以下示例展示了如何编写代码。在这段代码中,定义了一个抽象的 Shape 类和两个具体子类。

    1. public abstract class Shape {
    2. public abstract double area(); // 两个抽象方法
    3. public abstract double circumference(); // 注意,没有主体,只有分号
    4. }
    5. class Circle extends Shape {
    6. public static final double PI = 3.14159265358979323846;
    7. protected double r; // 实例字段
    8. public Circle(double r) { this.r = r; } // 构造方法
    9. public double getRadius() { return r; } // 访问器
    10. public double area() { return PI*r*r; } // 实现超类中的
    11. public double circumference() { return 2*PI*r; } // 两个抽象方法
    12. }
    13. class Rectangle extends Shape {
    14. protected double w, h; // 实例字段
    15. public Rectangle(double w, double h) { // 构造方法
    16. this.w = w; this.h = h;
    17. }
    18. public double getWidth() { return w; } // 访问器方法
    19. public double getHeight() { return h; } // 另一个访问器
    20. public double area() { return w*h; } // 实现超类中的
    21. public double circumference() { return 2*(w + h); } // 两个抽象方法
    22. }

    Shape 类中每个抽象方法的括号后面都是分号,没有花括号,也没定义方法的主体。使用以上示例中定义的这几个类可以编写如下的代码:

    1. Shape[] shapes = new Shape[3]; // 创建一个保存形状的数组
    2. shapes[0] = new Circle(2.0); // 填充这个数组
    3. shapes[1] = new Rectangle(1.0, 3.0);
    4. shapes[2] = new Rectangle(4.0, 2.0);
    5. double totalArea = 0;
    6. for(int i = 0; i < shapes.length; i++)
    7. totalArea += shapes[i].area(); // 计算这些形状的面积

    有两点要注意。

    • Shape 类的子类对象可以赋值给 Shape 类型数组中的元素,无需校正。这又是一个放大转换引用类型的例子。
    • 即便 Shape 类没有定义 area()circumference() 方法的主体,各个 Shape 对象还是能调用这两个方法。调用这两个方法时,使用虚拟方法查找技术找到要调用的方法。因此,圆的面积使用 Circle 类中定义的方法计算,矩形的面积使用 Rectangle 类中定义的方法计算。