9.1 抽象类
在面向对象的世界里,所有的对象都是通过类来实例化的,但并不是所有的类都是直接用来实例化对象的。如果一个类中没有包含足够的信息来描绘一个具体的事务,这样的类可以形成抽象类。
抽象类往往用来表示在对事务进行分析、设计后得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。例如,如果进行一个图形编辑软件的开发,就会发现需要操作圆、三角形这样一些具体的图形概念。这些具体的概念虽然是不同的,但是它们又都属于形状这样一个不是真实存在的抽象概念,这个抽象的概念是不能实例化出一个具体的形状对象的。
9.1.1 抽象类的概念
在面向对象分析和设计的过程中,经过抽象、封装和继承的分析之后,会需要想创建这样一个抽象的父类,该父类定义了其所有子类共享的一般形式,具体细节由子类来完成。
这样的父类作为规约,其需要子类完成的方法在父类中往往是空方法,方法本身没有实际意义。而且这些父类本身就比较抽象,根据这些抽象的父类实例化出的对象通常也缺乏实际意义,更多的是利用父类的规约创建出子类,再使用子类实例化出有意义的对象。
Java中提供了一种专门供子类来继承的类,这个类就是抽象类,其语法形式如下。
abstract class 类名{
}
Java也提供了一种特殊的方法,这个方法不是一个完整的方法,只含有方法的声明,没有方法体,这样的方法叫做抽象方法,其语法形式如下。
其他修饰符abstract返回值 方法名( );
9.1.2 抽象类的使用
接下来通过一个例子,了解抽象类的使用。
现有Person类、Chinese类和American类三个类,其中Person类为抽象类,含有eat()和work()两个抽象方法,其类关系如图9.1所示。
Person类的代码如下。
abstract class Person
{
String name = "人";
String color = "肤色";
//定义吃饭的抽象方法eat()
public abstract void eat();
//定义工作的抽象方法eat()
public abstract void work();
}
Chinese类代码如下。
//子类Chinese继承自抽象父类Person
class Chinese extends Person
{
String houseHold = "北京"; //户口
//实现父类eat()的抽象方法
public void eat()
{
System.out.println("中国人用筷子吃饭!");
}
//实现父类work()的抽象方法
public void work()
{
System.out.println("中国人勤劳工作!");
}
}
American类代码如下。
//子类American继承自抽象父类Person
class American extends Person
{
String belief = "基督教"; //信仰
//实现父类eat()的抽象方法
public void eat()
{
System.out.println("美国人用刀叉吃饭!");
}
//实现父类work()的抽象方法
public void work()
{
System.out.println("美国人快乐工作!");
}
}
测试类代码如下。
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(); //调用美国人工作的方法
}
}
程序运行结果如图9.2所示。
9.1.3 抽象类的特征
在上面例子的基础上,可以进一步了解抽象类的特征。
(1)抽象类不能被直接实例化。
例如,在测试类代码中写如下的语句。
Person liuHL = new Person();
编译时就会报错,提示抽象类无法被实例化,如图9.3所示。
(2)抽象类的子类必须实现抽象方法,除非子类也是抽象类。
抽象类是父类对子类的规约,要求子类必须实现抽象父类的抽象方法。例如,如果将Chinese类的work方法变为注释,使抽象类中的抽象方法没有被子类实现,编译时报错,如图9.4所示。
(3)抽象类里可以有普通方法,也可以有抽象方法,但是有抽象方法的类必须是抽象类。
去掉Person类前的abstract关键字,使Person类不再是抽象类,却含有抽象方法,编译时报错,如图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方法
}
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方法
}
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); //调用驾驶员对象的相应方法
}
}
运行结果如图9.6所示。