9.1 抽象类

  在面向对象的世界里,所有的对象都是通过类来实例化的,但并不是所有的类都是直接用来实例化对象的。如果一个类中没有包含足够的信息来描绘一个具体的事务,这样的类可以形成抽象类。

  抽象类往往用来表示在对事务进行分析、设计后得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。例如,如果进行一个图形编辑软件的开发,就会发现需要操作圆、三角形这样一些具体的图形概念。这些具体的概念虽然是不同的,但是它们又都属于形状这样一个不是真实存在的抽象概念,这个抽象的概念是不能实例化出一个具体的形状对象的。

9.1.1 抽象类的概念

  在面向对象分析和设计的过程中,经过抽象、封装和继承的分析之后,会需要想创建这样一个抽象的父类,该父类定义了其所有子类共享的一般形式,具体细节由子类来完成。

  这样的父类作为规约,其需要子类完成的方法在父类中往往是空方法,方法本身没有实际意义。而且这些父类本身就比较抽象,根据这些抽象的父类实例化出的对象通常也缺乏实际意义,更多的是利用父类的规约创建出子类,再使用子类实例化出有意义的对象。

  Java中提供了一种专门供子类来继承的类,这个类就是抽象类,其语法形式如下。

abstract class 类名{

}
copy

  Java也提供了一种特殊的方法,这个方法不是一个完整的方法,只含有方法的声明,没有方法体,这样的方法叫做抽象方法,其语法形式如下。

  其他修饰符abstract返回值 方法名( );

9.1.2 抽象类的使用

  接下来通过一个例子,了解抽象类的使用。

  现有Person类、Chinese类和American类三个类,其中Person类为抽象类,含有eat()和work()两个抽象方法,其类关系如图9.1所示。

图9.1 抽象类之间的类图关系

  Person类的代码如下。

abstract class Person 

{

    String name = "人";

    String color = "肤色";

    //定义吃饭的抽象方法eat()

    public abstract void eat();

    //定义工作的抽象方法eat()

    public abstract void work();

}
copy

  Chinese类代码如下。

//子类Chinese继承自抽象父类Person

class Chinese extends Person

{

    String houseHold = "北京";                              //户口



    //实现父类eat()的抽象方法

    public void eat()

    {

        System.out.println("中国人用筷子吃饭!");

    }

    //实现父类work()的抽象方法

    public void work()

    {

        System.out.println("中国人勤劳工作!");

    }

}
copy

  American类代码如下。

//子类American继承自抽象父类Person

class American extends Person

{

    String belief = "基督教";                         //信仰



    //实现父类eat()的抽象方法

    public void eat()

    {

        System.out.println("美国人用刀叉吃饭!");

    }

    //实现父类work()的抽象方法

    public void work()

    {

        System.out.println("美国人快乐工作!");

    }

}
copy

  测试类代码如下。

class TestAbstract

{

    public static void main(String[] args) 

    {

        Person liuHL = new Chinese();      //创建一个中国人对象

        System.out.println("***中国人的行为***");

        liuHL.eat();                                               //调用中国人吃饭的方法

        liuHL.work();                                 //调用中国人工作的方法

        Person jacky = new American();    //创建一个美国人对象

        System.out.println("***美国人的行为***");

        jacky.eat();                                      //调用美国人吃饭的方法

        jacky.work();                                  //调用美国人工作的方法

    }

}
copy

  程序运行结果如图9.2所示。

图9.2 抽象类使用

9.1.3 抽象类的特征

  在上面例子的基础上,可以进一步了解抽象类的特征。

  (1)抽象类不能被直接实例化。

  例如,在测试类代码中写如下的语句。

Person liuHL = new Person();
copy

  编译时就会报错,提示抽象类无法被实例化,如图9.3所示。

图9.3 抽象类无法被实例化

  (2)抽象类的子类必须实现抽象方法,除非子类也是抽象类。

  抽象类是父类对子类的规约,要求子类必须实现抽象父类的抽象方法。例如,如果将Chinese类的work方法变为注释,使抽象类中的抽象方法没有被子类实现,编译时报错,如图9.4所示。

图9.4 抽象方法必须被实现

  (3)抽象类里可以有普通方法,也可以有抽象方法,但是有抽象方法的类必须是抽象类。

  去掉Person类前的abstract关键字,使Person类不再是抽象类,却含有抽象方法,编译时报错,如图9.5所示。

图9.5 有抽象方法的类必须是抽象类

  需要注意的是,抽象类里面也可以没有抽象方法,只是把原来的类前面加上abstract关键字,使其变为抽象类。

9.1.4 抽象类的应用

  再分析《租车系统》,很自然地就会想到,之前Vehicle类中的show()方法是一个空方法,没有实际意义,所以可以把它定义为抽象方法。

  另外,在讲解继承的时候,Truck类重写了Vehicle类的drive()方法,而且通过需求可以判断出,如果还有其他类需要继承自Vehicle类,也可能需要重写drive()方法,实现各自行驶的功能。所以,可以把Vehicle类的drive()方法定义为抽象方法,把原来Vehicle类中drive()方法的方法体实现代码移到Car类中,相当于Car类实现Vehicle类drive()抽象方法。

  修改后Vehicle类的代码如下。

package com.bd.zuche;

//车类,是父类,抽象类

public abstract class Vehicle 

{

    String name = "汽车";                   //车名

    int oil = 20;                                              //油量

    int loss = 0;                                              //车损度



    //抽象方法,显示车辆信息

    public abstract void show();

    //抽象方法,行驶

    public abstract void drive();

    //加油

    public void addOil()

    {

        if(oil > 40)                   

        {

            oil = 60;

            System.out.println("邮箱已加满!");

        }else{                  

            oil = oil + 20;

        }

        System.out.println("加油完成!");

    }

    //省略了构造方法、getter方法

}
copy

  Car类的代码如下。

package com.bd.zuche;

//轿车类,是子类,继承Vehicle类

public class Car extends Vehicle 

{

    private String brand = "红旗";       //品牌

    //子类重写父类的show()抽象方法

    public void show()

    {

        System.out.println("显示车辆信息:\n车辆名称为:" + this.name + " 品牌是:" + this.brand + " 

        油量是:" + this.oil + " 车损度为:" + this.loss);

        //System.out.println("显示车辆信息:\n车辆名称为:" + getName() + " 品牌是:" + this.brand + "

        //油量是:" + getOil() + " 车损度为:" + getLoss());

     }       

        //子类重写父类的drive()抽象方法

     public void drive()

     {

        if(oil < 10)

        {       

            System.out.println("油量不足10升,需要加油!");

        }else{

            System.out.println("正在行驶!");

            oil = oil - 5;

            loss = loss + 10;

        }

    }

//省略了构造方法、getter方法

}
copy

  Truck类和Driver类的代码都没发生变化,运行测试类代码如下。

import com.bd.zuche.*;

class TestZuChe 

{

    public static void main(String[] args) 

    {

        Vehicle car = new Car("战神","长城");                 //初始化轿车对象car

        Vehicle truck = new Truck("大力士二代","10吨");          //初始化卡车对象truck

        Driver d1 = new Driver("柳海龙");                    //创建并初始化驾驶员对象

        d1.callShow(car);                       //调用驾驶员对象的相应方法

        d1.callShow(truck);                         //调用驾驶员对象的相应方法

    }

}
copy

  运行结果如图9.6所示。

图9.6 用抽象类完成“租车系统”