7.5  抽象——abstract

抽象不仅是面向对象设计的重要指导思想,在 Java 中也是编写类的实际语法格式。这一小节首先从继承中超类的抽象特性,讨论如何设计和编写抽象类。并且利用实例解释抽象在编程中的具体应用。

7.5.1  抽象就是高度概括

观察和分析一些继承链和超类,如 API 中的继承链,以及一些超类,如 Object Exception ,以及编程人员自己设计的继承链,如 CircleShape ,不难看出,沿着继承链越往上的类就越抽象。正因为抽象,所以涵盖的面就越广。因而可以说,抽象就是高度概括。
抽象类的设计思想正是基于这个事实和出发点。例如在计算圆形物体的面积和体积的程序中,作为超类的 CircleShape 正是抽象性地概括了所有圆形物体具有的共同特性——半径。无论是什么样的圆形物体,都具有半径这个事实。当然,给出圆形物体上的任意两点,也可以确定其半径,因而它也涵盖了用两点来确定半径的运算和操作。当然,一些特殊的圆形物体,如圆棱柱、环形物体,扇形物体,等等,不只仅有半径,而且必须有其他参数来确定和描述它们。这些特殊性,不是高度概括的特性,也不具有代表性,因而不能包括在超类,如 CircleShape 中。
如果把问题再提升到广义,如定义所有的几何物体,而不只是圆形。则可在 CircleShape 上加入更抽象的超类,如 Shape Shape 类不仅包括圆形几何体,也包括如长方形体、三角形体,以及多边形体在内的其他几何物体。甚至涵盖这些基本几何形体的综合形体,等等。那么,经过高度抽象的 Shape 这个类涵盖所有几何物体的物理特征即是两点的坐标。
抽象类除高度概括它代表的所有对象的形态描述之外,还起到制定运算和操作规范以及模式化的作用。虽然在抽象类这一级,要具体完善运算和操作内容是不现实,也是不可能的。但它对具体的重要的运算和操作,可以起到政策制定者和指导者的领导角色。其他所有在这个继承链上的子类,都以它为楷模,来执行具体完善和补充代码编写任务。如, Shape 类可以对计算表面积和体积的方法进行签名性的描述。对它所代表的所有对象共享的静态数据和静态方法进行定义,等等。
由于抽象类的高度概括性,由它来创建对象将是毫无意义的。对这一点, Java 已经做了相应的规定,即抽象类只能用来引用,不能够创建对象。关于这个内容,将在第 8 章多态性中详细讨论。
下面讨论如何编写抽象类。

7.5.2  抽象类和抽象方法

抽象类使用关键字 abstract 来定义。其语法格式如下:
 
public abstract class ClassName {
    // 定义数据
    ...
    // 定义方法
    ...
    // 也可能定义抽象方法
    public abstract returnTye methodName(argumentList)
}
 
一个抽象类可以包括各种类型数据、构造器以及方法。虽然无抽象方法的抽象类属合法,但通常抽象类定义抽象方法。抽象方法的定义也使用关键字 abstract 。抽象方法只是对这个方法的声明,是没有程序体的还未实现的方法,必须由继承它的某个子类来完善。这也证明了抽象类不能够用来创建对象;或者说使用抽象类来创建对象是没有实际意义的。抽象类的构造器只能在内部通过子类创建的对象调用。
如上一小节讨论,抽象类是高层次的类,通常在一个继承链的顶端,对它所代表的所有对象进行高度概括和描述。一个解决更复杂实际问题的继承链可能有多个抽象类。
下面来讨论一些具体例子。
1. CircleShape 修改为抽象类。
 
// 完整的程序存在本书配套资源目录 Ch7 中名为 CircleShape.java
public abstract class CircleShape {
    protected double radius;
    protected double x1, y1, x2, y2;
    // 所有其他语句
    ...
    public abstract void computeArea();     // 定义抽象方法
    public abstract void computeValume();      
}
 
2. 定义一个广义上的代表所有人的抽象类 Person
 
// 这个程序存在本书配套资源目录 Ch7 中名为 Person.java
//abstract class
public abstract class Person {
    protected String lastName, firstName;
    protected char sex;
    protected byte age;
    protected String address;
    public Person(String lastName, String firstName, char sex, byte age,
    String address) {
        this.lastName = lastName;
        this.firstName = firstName;
        this.sex = sex;
        this.age = age;
        this.address = address;
    }
    public String toString() {
        String message = "Last name: " + lastName + ", " + "first name: "
        + firstName + "\n"
                        + "Sex: " + sex + "\n"
                        + "Age: " + age + "\n"
                        + "Address: " + address + "\n";
         return message;
        }
}
 
这个抽象类可以应用在所有与人有关的软件上,因为它涵盖代表所有人的最基本信息和操作。由于在这个层次没有涉及到解决问题的领域,所以只提供覆盖的 toString() 方法,来返回 Person 的信息。
如果编写一个计算雇员工资的软件,可以利用这个超抽象类继承出另一个计算雇员工资的抽象超类 Employee2 ,如:
 
// 这个程序存在本书配套资源目录 Ch7 中名为 Employees.java
public abstract class Employee2 extends Person {
    protected String employeeID;
    protected String jobTitle;
    protected byte seniority;
    protected double salary;
 
    public Employee2(String lastName, String firstName, char sex, byte age,
                    String address, String employeeID, String jobTitle, byte
                    seniority) {
        super(lastName, firstName, sex, age, address);
        this.jobTitle = jobTitle;
        this.seniority = seniority;
    }
    public abstract void computeSalary();   // 声明抽象方法
    public String toString() {              // 覆盖 Person toString() 方法
        String message = super.toString() + "EmployeeID: " + employeeID +
        "\n"
                        + "jobTitle: " + jobTitle + "\n"
                        + "Seniority: " + seniority + "\n";
        return message;
    }
}
 
3W  抽象类是对由它代表的所有对象的高度概括,处于继承链的顶部。抽象类通过关键字 abstract 来定义。抽象类中可以定义任何类型的数据、构造器以及方法。一般情况下,抽象类中定义抽象方法。抽象方法通过关键字 abstract 定义。抽象方法只是方法的声明,而没有程序体。抽象类正是通过定义抽象方法对其子类的运算和操作制定规范。继承链上的非抽象子类必须完善抽象方法。