
6.5 抽象类的基本概念
前面对类的继承进行了初步的讲解。通过继承,可以从原有的类派生出新的类。原有的类称为基类或父类,而新的类则称为派生类或子类。通过这种机制,派生出的新的类不仅可以保留原有的类的功能,而且还可以拥有更多的功能。
除了上述的机制之外,在Java也可以创建一种类专门用来当作父类,这种类称为“抽象类”。抽象类的作用有点类似“模版”,其目的是要设计者依据它的格式来修改并创建新的类。但是并不能直接由抽象类创建对象,只能通过抽象类派生出新的类,再由它来创建对象。但是在抽象类的使用中同样存在单继承的局限,即一个子类只能继承一个抽象类。
抽象类的定义及使用规则如下:
(1)包含一个抽象方法的类必须是抽象类;
(2)抽象类和抽象方法都要使用abstract关键字声明;
(3)抽象方法只需声明而不需要实现;
(4)抽象类必须被子类继承,子类(如果不是抽象类)必须覆写抽象类中的全部抽象方法。
【格式6-2 抽象类的定义格式】

从上面的格式中可以发现,抽象类的定义就是比普通类多了一些抽象方法而已,其他地方与普通类的组成基本上都是一样的。
【例6.20】定义一个抽象类

在以上的A类中,因为定义了print()的抽象方法,所以此类的声明为abstract class。
定义完成一个抽象类之后,下面就可以编写一个子类继承此抽象类。需要注意的是,子类必须覆写抽象类中的全部抽象方法。
【例6.21】继承抽象类

程序执行结果:

上面程序完成了抽象类的基本操作,子类覆写了抽象方法,之后在主方法中,通过子类的实例化对象就可以调用被子类覆写过的方法了。以上程序的类图如图6-9所示。
在图6-9中,抽象类使用了{abstract}的方式表示出来,而在有些时候,抽象类的图形也可以通过类名加斜体的方式表示,如图6-10所示。

图6-9 抽象类的图形表示

图6-10 抽象类的另一种表现形式
提示
抽象类与普通类的最大区别在于强制。
如果使用普通类,那么子类可以根据自己的需要选择性的进行某些父类方法的覆写,所以普通类无法对子类覆写的方法进行限制。然而抽象类却可以强制性要求子类覆写父类方法,正因为如此,在实际的开发中,不建议让子类继承普通类,而只建议子类继承抽象类。
了解抽象类的概念之后,下面再来思考以下的两个问题。
(1)一个抽象类可以使用final关键字声明吗?
(2)一个抽象类中可以定义构造方法吗?
对于第1个问题,实际上很容易回答,从之前讲解的概念可以知道,一个类如果使用了final关键字声明,则此类不能被子类继承,而抽象类又必须被子类覆写,所以很明显,第1个问题的答案是:“一个抽象类不能使用final关键字声明”。
注意
抽象方法不要使用private声明。
在使用abstract关键字修饰抽象方法的时候不能使用private修饰,因为抽象方法必须被子类覆写,而如果使用了private声明,则子类是无法覆写的。
第2个问题,可能会有不少的读者难以回答,实际上在一个抽象类中是允许存在构造方法的,因为抽象类依然使用的是类的继承关系,而且抽象类中也存在各个属性,所以子类在实例化之前肯定是先要对父类进行实例化的。
【例6.22】在抽象类中定义构造方法

程序执行结果:

从程序中可以发现,抽象类中定义了构造方法,但是定义的抽象方法并不能被外部所直接调用,在子类对象实例化之前也同样会默认调用父类中的无参构造,也就是说此时的子类实际上也隐含了一个super关键字调用构造方法的语句。
【例6.23】super关键字调用构造方法

既然子类可以通过super调用抽象类中的构造方法,那么也可以直接在子类指定调用父类中的指定参数的构造方法。
【例6.24】调用抽象类中指定参数的构造方法

程序执行结果:

以上程序在实例化Student类的时候先调用了抽象类中的构造方法,将姓名和年龄进行设置,然后再通过覆写的getInfo()方法输出信息。
提示
抽象类与普通类。
通过代码读者可以清楚地发现,实际上抽象类就是比普通类多定义了一个抽象方法而已,除了不能直接进行对象的实例化操作之外并没有任何的不同。