9.1 抽象类

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

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

9.1.1 抽象类的概念

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

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

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

  1. abstract class 类名{
  2. }

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

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

9.1.2 抽象类的使用

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

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

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

  Person类的代码如下。

  1. abstract class Person
  2. {
  3. String name = "人";
  4. String color = "肤色";
  5. //定义吃饭的抽象方法eat()
  6. public abstract void eat();
  7. //定义工作的抽象方法eat()
  8. public abstract void work();
  9. }

  Chinese类代码如下。

  1. //子类Chinese继承自抽象父类Person
  2. class Chinese extends Person
  3. {
  4. String houseHold = "北京"; //户口
  5. //实现父类eat()的抽象方法
  6. public void eat()
  7. {
  8. System.out.println("中国人用筷子吃饭!");
  9. }
  10. //实现父类work()的抽象方法
  11. public void work()
  12. {
  13. System.out.println("中国人勤劳工作!");
  14. }
  15. }

  American类代码如下。

  1. //子类American继承自抽象父类Person
  2. class American extends Person
  3. {
  4. String belief = "基督教"; //信仰
  5. //实现父类eat()的抽象方法
  6. public void eat()
  7. {
  8. System.out.println("美国人用刀叉吃饭!");
  9. }
  10. //实现父类work()的抽象方法
  11. public void work()
  12. {
  13. System.out.println("美国人快乐工作!");
  14. }
  15. }

  测试类代码如下。

  1. class TestAbstract
  2. {
  3. public static void main(String[] args)
  4. {
  5. Person liuHL = new Chinese(); //创建一个中国人对象
  6. System.out.println("***中国人的行为***");
  7. liuHL.eat(); //调用中国人吃饭的方法
  8. liuHL.work(); //调用中国人工作的方法
  9. Person jacky = new American(); //创建一个美国人对象
  10. System.out.println("***美国人的行为***");
  11. jacky.eat(); //调用美国人吃饭的方法
  12. jacky.work(); //调用美国人工作的方法
  13. }
  14. }

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

9.1 抽象类 - 图2
图9.2 抽象类使用

9.1.3 抽象类的特征

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

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

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

  1. Person liuHL = new Person();

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

9.1 抽象类 - 图3
图9.3 抽象类无法被实例化

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

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

9.1 抽象类 - 图4
图9.4 抽象方法必须被实现

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

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

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

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

9.1.4 抽象类的应用

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

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

  修改后Vehicle类的代码如下。

  1. package com.bd.zuche;
  2. //车类,是父类,抽象类
  3. public abstract class Vehicle
  4. {
  5. String name = "汽车"; //车名
  6. int oil = 20; //油量
  7. int loss = 0; //车损度
  8. //抽象方法,显示车辆信息
  9. public abstract void show();
  10. //抽象方法,行驶
  11. public abstract void drive();
  12. //加油
  13. public void addOil()
  14. {
  15. if(oil > 40)
  16. {
  17. oil = 60;
  18. System.out.println("邮箱已加满!");
  19. }else{
  20. oil = oil + 20;
  21. }
  22. System.out.println("加油完成!");
  23. }
  24. //省略了构造方法、getter方法
  25. }

  Car类的代码如下。

  1. package com.bd.zuche;
  2. //轿车类,是子类,继承Vehicle类
  3. public class Car extends Vehicle
  4. {
  5. private String brand = "红旗"; //品牌
  6. //子类重写父类的show()抽象方法
  7. public void show()
  8. {
  9. System.out.println("显示车辆信息:\n车辆名称为:" + this.name + " 品牌是:" + this.brand + "
  10. 油量是:" + this.oil + " 车损度为:" + this.loss);
  11. //System.out.println("显示车辆信息:\n车辆名称为:" + getName() + " 品牌是:" + this.brand + "
  12. //油量是:" + getOil() + " 车损度为:" + getLoss());
  13. }
  14. //子类重写父类的drive()抽象方法
  15. public void drive()
  16. {
  17. if(oil < 10)
  18. {
  19. System.out.println("油量不足10升,需要加油!");
  20. }else{
  21. System.out.println("正在行驶!");
  22. oil = oil - 5;
  23. loss = loss + 10;
  24. }
  25. }
  26. //省略了构造方法、getter方法
  27. }

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

  1. import com.bd.zuche.*;
  2. class TestZuChe
  3. {
  4. public static void main(String[] args)
  5. {
  6. Vehicle car = new Car("战神","长城"); //初始化轿车对象car
  7. Vehicle truck = new Truck("大力士二代","10吨"); //初始化卡车对象truck
  8. Driver d1 = new Driver("柳海龙"); //创建并初始化驾驶员对象
  9. d1.callShow(car); //调用驾驶员对象的相应方法
  10. d1.callShow(truck); //调用驾驶员对象的相应方法
  11. }
  12. }

  运行结果如图9.6所示。

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