讲解
有时父类本身没有完整实现的意义:「形状」怎么算面积?得看具体是圆还是方。抽象类(abstract class)就是为这种「半成品图纸」准备的:abstract class Shape 不能被 new(new Shape() 编译报错),只能被继承;里面的抽象方法 abstract double area(); 只有签名没有方法体,强迫每个子类必须给出自己的实现——少写一个都编译不过。抽象类也可以有普通的字段和已实现的方法,子类照常继承复用。
抽象类表达的是「是什么加上部分怎么做」:它在继承体系里卡位,把共性代码(字段、已实现方法)收拢在父类,把差异点(抽象方法)下放给子类。一个经典套路是「模板方法」:父类定义一个流程方法,里面按固定顺序调用若干抽象步骤,子类只需实现各个步骤,整个流程的骨架由父类把控——既复用了流程,又允许步骤各异。
没有抽象方法的类也可以声明成 abstract,效果仅仅是禁止实例化。判断要不要抽象类的经验法则:这个父类有没有「完整造出来」的语义?「动物」「形状」「支付方式」这种概念本身就造不出有意义的实例,就该是抽象的。另外抽象类和「全是抽象方法的类型」是有区别的——后者演进成了接口,下一章专门讲,两者的选择(单继承的抽象类 vs 多实现的接口)到那时再对比。
示例
抽象类定义抽象方法,子类各自实现:
abstract class Shape {
protected String name;
Shape(String name) {
this.name = name;
}
abstract double area();
public void describe() {
System.out.println(name + " 的面积是 " + String.format("%.2f", area()));
}
}
class Circle extends Shape {
private final double radius;
Circle(double radius) {
super("圆形");
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
class Square extends Shape {
private final double side;
Square(double side) {
super("正方形");
this.side = side;
}
@Override
double area() {
return side * side;
}
}
public class Main {
public static void main(String[] args) {
Shape c = new Circle(2);
Shape s = new Square(3);
c.describe();
s.describe();
}
}
模板方法模式:父类控制流程骨架,子类实现差异步骤:
abstract class Beverage {
public final void prepare() {
boilWater();
brew();
pourInCup();
}
private void boilWater() {
System.out.println("烧水");
}
abstract void brew();
private void pourInCup() {
System.out.println("倒入杯中");
}
}
class Tea extends Beverage {
@Override
void brew() {
System.out.println("冲泡茶叶");
}
}
class Coffee extends Beverage {
@Override
void brew() {
System.out.println("滴滤咖啡粉");
}
}
public class Main {
public static void main(String[] args) {
System.out.println("--- 泡茶 ---");
new Tea().prepare();
System.out.println("--- 冲咖啡 ---");
new Coffee().prepare();
}
}
常见坑
- 试图 new 一个抽象类:抽象类生来就是被继承的,new Shape() 编译报错。要实例就得 new 具体子类。
- 子类漏实现抽象方法:普通类继承抽象类却没实现全部抽象方法,编译失败;除非子类自己也声明成 abstract,把义务继续往下传。
- 抽象方法乱加修饰符:abstract 和 private、final 天然矛盾(一个要求子类实现,一个禁止子类碰),写在一起直接编译错误。
- 抽象类与接口傻傻分不清:抽象类能存状态(字段)且走单继承,接口表达能力契约且可多实现。下一章详谈接口后再回看这条。
小结
抽象类不能实例化,抽象方法强制子类实现;模板方法让父类控流程、子类填步骤。下一节学习纯粹的「能力契约」:接口。