抽象类
一、抽象类
首先考虑“点” 和“长方形”这两个图形,这两个类中都有用于绘图的方法draw;下面来设计这两个类。
点类Point
这是表示点的类,不持有字段。方法draw的实现如下,只显示一个符号字符'+'
void draw() {
System.out.println ('+');
}
长方形类Rectangle
这是表示长方形的类,持有表示长和宽的int型字段width和height,方法draw的实现和显示如下:
void draw() [
for (int i = 1; i <= height; i++){
for (int j = 1; j <= width; j++)
System.out.print('*');
System.out.println ();
}
}
即使在单独定义的类中创建了方法draw, 它们也是毫无关系的,从“图形” 类派生出“点” 和“长方形”
图形类Shape
这是表示图形的类,点、长方形和直线等类都直接或间接派生自该类。
思考1:方法draw应该执行什么操作呢?
应该显示什么呢?找不出合适的内容
思考2:该怎样来创建实例呢?
虽然还未设计各个类的构造函数,但应该如下面这样执行调用来创建实例
类Point···new Point() ---无参数
类Rectangle···new Rectangle(4, 3)---传入长和宽
类Shape的实例无法按照这样的形式进行创建。看不出应该传递什么样的参数
类Shape与其说是图形的设计图, 倒不如说是表示图形这一概念的抽象设计图。
像Shape这样表示具有下述性质的类的就是抽象类:
1)无法创建或者不应该创建实例;
2)无法定义方法的主体,其内容应该在子类中具体实现
如果将类Shape定义为抽象类. 则如下所示:
abstract class Shape{
abstract void draw();
}
类Shape和方法draw的声明中都加上了abstract关键字,从该类派生的类Point和类Rectangle都不是抽象类,而是普通(非抽象)的类。
这三个类的类层次图如图,抽象类的名称用斜体表示:
将类Shape设计为抽象类,并从中派生类Point和类Rectangle,程序如下:
// 图形类群
//===== 图形 =====//
abstract class Shape {
abstract void draw(); // 绘图(抽象方法)
}
//===== 点 =====//
class Point extends Shape {
Point() { } // 构造函数
void draw() { // 绘图
System.out.println('+');
}
}
//===== 长方形 =====//
class Rectangle extends Shape {
private int width; // 长
private int height; // 宽
Rectangle(int width, int height) { // 构造函数
this.width = width;
this.height = height;
}
void draw() { // 绘图
for (int i = 1; i <= height; i++) {
for (int j = 1; j <= width; j++)
System.out.print('*');
System.out.println();
}
}
}
二、抽象方法
类Shape中的方法draw的开头加上了abstract。像这样声明的方法就是抽象方法,方法的前面加上的abstract蕴含了如下含义:
在这里(我的类中)不可以定义方法的主体,请在我派生的类中定义!
由于抽象方法中不存在主体、因此在其声明中,将{}替换为 ";"即使方法的主体为空,也不可以写为程序块{}
abstract void draw() { } //错误【无法定义】
类Point和类Rectangle中重写了方法draw, 并定义了主体,像这样在从抽象类派生的类中,重写抽象方法、定义主体的操作称为实现方法【重写超类中的抽象方法,声明方法主体的定义的操作称为实现方法】(类Point和类Rectangle中就实现了抽象类Shape中的方法draw)
像类Shape这样,只要包含1个抽象方法,该类就必须声明为抽象类,为了将类声明为抽象类,需要在class的前面加上absract,不过,不包含抽象方法的类也可以声明为抽象类【对于抽象类,不可以指定final、Static、private】
// 图形类群的使用示例
class ShapeTester {
public static void main(String[] args) {
// 下述声明错误:抽象类无法实例化
// Shape s = new Shape();
Shape[] a = new Shape[2];
a[0] = new Point(); // 点
a[1] = new Rectangle(4, 3); // 长方形
for (Shape s : a) {
s.draw(); // 绘图
System.out.println();
}
}
}
输出:
无法创建抽象类的实例
上述代码中s的声明发生错误(注释掉了),由于抽象类中的方法未具体定义,因此无法使用new Shape()来创建实例
抽象类和多态
a是Shape类型的数组。其元素a[0]和a[1]为Shape类型的类类型变量,引用了派生自Shape的类的实例;
如图,a[0]引用的是类Point类型的实例,a[l]引用的是类Rectangle类型的实例,通过扩展for语句对数组a中的元素调用方法draw,对第1个元索调用类Point的方法draw,对第2个元素调用类Rectangle的方法draw,这从运行结果中也可以得到确认
抽象类Shape并不是具体的图形,而是表示图形概念的类,抽象类虽然是无法创建主体的不完整的类,但可以让包含自身在内的派生类具有“血缘关系”【如果用于对下位类进行分组,充分利用多态的类中没有具体的主体,则可以将其定义为抽象类】
如果从抽象类派生的子类中未实现抽象方法,则抽象方法会被直接继承,如图:
抽象类A中的两个方法 a 和 b 都是抽象方法,从类A派生的、未实现方法b的类B也是抽象类。
如果类B的声明中省略abstract,就会发生编译错误;从类B派生的类C中囚为实现了方法b, 所以它就不再是抽象类
三、总结
1、声明中加上abstract的方法是抽象方法,抽象方法没有方法主体;
2、只要包含一个抽象方法,类就必须定义为抽象类,抽象类的声明中需要加上abstract;
3、无法创建抽象类的实例;
4、抽象类的类类型变量可以引用该类的下位类实例,因此可以灵活应用多态;
5、抽象类可以对它派生的下位类群进行分组,让它们具有“血缘关系”;
6、重写超类中的抽象方法,定义方法主体的操作称为实现方法;
7、方法中可以调用同一个类中的、没有主体的抽象方法。要调用的方法会在程序运行时通过动态联编来确定(调用与实例的类型相对应的方法)
8、超类中的非抽象方法可以在子类中被重写为抽象方法;
9、通过将Object类的非抽象方法public String toString ()重写为抽象方法,可以强制该类的下位类实现toString方法