抽象类是Java面向对象编程体系中的重要组成部分,它介于普通类和接口之间,既可以提供部分方法的实现,也可以定义需要子类强制实现的方法规范,是平衡代码复用和约束子类行为的重要设计手段。

抽象类的基本概念
抽象类是使用abstract关键字修饰的类,它不能被直接实例化,只能作为其他类的父类被继承。抽象类中可以包含抽象方法和具体实现的方法,其中抽象方法是没有方法体的方法,同样使用abstract关键字修饰,子类继承抽象类后,必须实现父类中所有的抽象方法,否则子类也需要被声明为抽象类。
下面是一个简单的抽象类定义示例:
// 定义抽象类Animal
abstract class Animal {
// 抽象方法,没有方法体,子类必须实现
public abstract void eat();
// 具体实现的方法,子类可以直接继承使用
public void sleep() {
System.out.println("动物正在睡觉");
}
}
抽象类的核心用途
1. 封装公共逻辑实现代码复用
当多个子类存在相同的逻辑实现时,可以把这些公共逻辑放到抽象类中,避免子类重复编写相同的代码。比如多个图形类都需要计算面积和周长,但是不同图形的计算方式不同,就可以把公共的属性放到抽象类中。
// 抽象图形类
abstract class Shape {
// 公共属性,所有图形都有颜色
protected String color;
public Shape(String color) {
this.color = color;
}
// 抽象方法,计算面积
public abstract double getArea();
// 抽象方法,计算周长
public abstract double getPerimeter();
// 公共方法,获取颜色
public String getColor() {
return color;
}
}
// 圆形子类
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
}
2. 定义子类的行为规范
抽象类中的抽象方法相当于给子类制定了必须遵循的行为规范,确保所有子类都具备某些核心能力。比如定义一个数据访问层的抽象类,要求所有子类都必须实现增删改查的基础方法,避免不同子类遗漏必要的方法实现。
// 数据访问层抽象类
abstract class BaseDao<T> {
// 新增数据
public abstract void add(T entity);
// 删除数据
public abstract void delete(int id);
// 修改数据
public abstract void update(T entity);
// 查询单条数据
public abstract T getById(int id);
// 查询所有数据
public abstract List<T> getAll();
}
3. 配合多态实现灵活的代码扩展
抽象类可以作为引用类型,指向不同的子类实例,实现运行时多态。当业务需求变化时,只需要新增抽象类的子类,不需要修改原有调用抽象类引用的代码,符合开闭原则。
public class Test {
public static void main(String[] args) {
// 抽象类引用指向子类实例
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.eat(); // 输出:狗吃骨头
animal2.eat(); // 输出:猫吃鱼
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
Java抽象类设计技巧
- 抽象类应该尽量只定义最核心的抽象方法,不要定义过多抽象方法,避免子类实现负担过重。
- 如果抽象类中的某个方法所有子类都有相同的实现,应该直接写成具体方法,而不是抽象方法,减少子类重复代码。
- 抽象类的构造方法应该设置为
protected权限,因为抽象类不能被实例化,构造方法只供子类调用。 - 不要在抽象类中定义过多状态,抽象类更适合封装行为规范和公共逻辑,状态过多可以考虑放到具体子类中。
- 如果抽象类中的抽象方法只有个别子类需要特殊实现,其他子类有通用实现,可以在抽象类中提供一个默认实现,子类按需重写。
抽象类和接口的区别
很多开发者会混淆抽象类和接口的使用场景,两者的核心区别如下:
| 对比维度 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | 使用abstract class定义 | 使用interface定义 |
| 方法类型 | 可以有抽象方法、具体方法、静态方法、私有方法 | Java8之后可以有默认方法、静态方法,不能有普通具体方法 |
| 属性类型 | 可以有各种访问权限的属性 | 属性默认是public static final的常量 |
| 继承规则 | 单继承,一个类只能继承一个抽象类 | 多实现,一个类可以实现多个接口 |
| 设计目的 | 代码复用+行为约束,体现is-a关系 | 定义能力规范,体现like-a关系 |
在实际开发中,如果需要复用代码并且定义子类的核心行为规范,优先选择抽象类;如果只需要定义能力规范,不需要复用代码,优先选择接口。