我们把 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声明。 - 使用
static、private和final声明的方法不能是抽象方法,因为这三种方法在子类中不能覆盖。类似地,final类中不能有任何abstract方法。 - 就算类中没有
abstract方法,这个类也能声明为abstract。使用这种方式声明的abstract类表明实现的不完整,要交给子类实现。这种类不能实例化。
下面通过一个示例说明这些规则的运作方式。 如果定义 Shape 类时把 area() 和 circumference() 声明为 abstract 方法,那么 Shape 的子类必须实现这两个方法才能实例化。也就是说,每个 Shape 对象都要确保实现了这两个方法。以下示例展示了如何编写代码。在这段代码中,定义了一个抽象的 Shape 类和两个具体子类。
public abstract class Shape {public abstract double area(); // 两个抽象方法public abstract double circumference(); // 注意,没有主体,只有分号}class Circle extends Shape {public static final double PI = 3.14159265358979323846;protected double r; // 实例字段public Circle(double r) { this.r = r; } // 构造方法public double getRadius() { return r; } // 访问器public double area() { return PI*r*r; } // 实现超类中的public double circumference() { return 2*PI*r; } // 两个抽象方法}class Rectangle extends Shape {protected double w, h; // 实例字段public Rectangle(double w, double h) { // 构造方法this.w = w; this.h = h;}public double getWidth() { return w; } // 访问器方法public double getHeight() { return h; } // 另一个访问器public double area() { return w*h; } // 实现超类中的public double circumference() { return 2*(w + h); } // 两个抽象方法}
Shape 类中每个抽象方法的括号后面都是分号,没有花括号,也没定义方法的主体。使用以上示例中定义的这几个类可以编写如下的代码:
Shape[] shapes = new Shape[3]; // 创建一个保存形状的数组shapes[0] = new Circle(2.0); // 填充这个数组shapes[1] = new Rectangle(1.0, 3.0);shapes[2] = new Rectangle(4.0, 2.0);double totalArea = 0;for(int i = 0; i < shapes.length; i++)totalArea += shapes[i].area(); // 计算这些形状的面积
有两点要注意。
- Shape 类的子类对象可以赋值给 Shape 类型数组中的元素,无需校正。这又是一个放大转换引用类型的例子。
- 即便 Shape 类没有定义
area()和circumference()方法的主体,各个 Shape 对象还是能调用这两个方法。调用这两个方法时,使用虚拟方法查找技术找到要调用的方法。因此,圆的面积使用Circle类中定义的方法计算,矩形的面积使用Rectangle类中定义的方法计算。
