1. 从动物园案例看抽象类的必要性
在面向对象编程中,继承机制让我们能够构建层次化的类结构。但当我们尝试用普通父类来描述某些现实场景时,往往会遇到一个根本性难题:有些行为在父类层面根本无法给出合理的具体实现。让我们通过动物园案例来深入理解这个痛点。
1.1 普通父类的局限性
假设我们要为动物园开发一个动物管理系统,最初可能会设计这样的类结构:
java复制class Animal {
public void eat() {
// 这里应该写什么?
}
public void speak() {
// 这里又该写什么?
}
}
这个设计存在明显问题:
- 对于
eat()方法:狗啃骨头、猫吃鱼、兔子吃胡萝卜,不同动物的进食方式差异巨大 - 对于
speak()方法:狗叫"汪汪"、猫叫"喵喵"、鸟叫"叽叽喳喳",根本无法统一
如果我们强行在父类中实现这些方法,结果会非常尴尬:
java复制class Animal {
public void eat() {
System.out.println("动物在吃东西"); // 毫无意义的空泛描述
}
public void speak() {
// 甚至无法写出任何有意义的实现
}
}
1.2 抽象行为的本质特征
这类问题的本质在于:某些行为在父类层面本身就是"抽象"的。这里的"抽象"指的是:
- 概念完整性:在父类层面,这个行为的概念是完整的(所有动物都需要进食和发声)
- 实现差异性:但具体到每个子类,这个行为的实现方式各不相同
- 强制必要性:子类必须提供自己的实现,不能简单继承父类的默认实现
这种场景下,普通类就显得力不从心,我们需要一种能够表达"抽象行为"的机制。
2. 抽象类的核心概念与语法
2.1 抽象类的基本定义
抽象类是Java中一种特殊的类,用abstract关键字修饰。它的核心特点是:
- 可以包含抽象方法(没有方法体的方法)
- 不能直接实例化
- 必须被继承才能使用
基本语法:
java复制abstract class 类名 {
// 抽象方法(无方法体)
public abstract 返回值类型 方法名(参数列表);
// 普通方法(有方法体)
public 返回值类型 方法名(参数列表) {
// 方法实现
}
}
2.2 抽象方法的特点
抽象方法是抽象类的核心,具有以下特征:
- 使用
abstract关键字修饰 - 没有方法体(即没有大括号内的实现代码)
- 必须以分号结尾
- 只能存在于抽象类或接口中
java复制// 正确的抽象方法声明
public abstract void makeSound();
// 错误示例1:缺少abstract关键字
public void makeSound(); // 编译错误
// 错误示例2:提供了方法体
public abstract void makeSound() {} // 编译错误
2.3 重构动物园案例
让我们用抽象类重构之前的动物园案例:
java复制abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
// 抽象方法
public abstract void eat();
public abstract void speak();
// 普通方法
public String getName() {
return name;
}
public void sleep() {
System.out.println(name + "正在睡觉...");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(getName() + "正在啃骨头");
}
@Override
public void speak() {
System.out.println("汪汪汪!");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(getName() + "正在吃鱼");
}
@Override
public void speak() {
System.out.println("喵喵喵!");
}
}
3. 抽象类的关键特性与规则
3.1 抽象类的构造方法
虽然抽象类不能直接实例化,但它可以有构造方法:
- 用于初始化抽象类中定义的成员变量
- 子类通过
super()调用父类构造方法 - 如果没有显式定义构造方法,编译器会提供默认无参构造
java复制abstract class Animal {
private String name;
// 抽象类的构造方法
public Animal(String name) {
this.name = name;
}
// ...
}
class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类构造方法
}
// ...
}
3.2 抽象类与普通方法的结合
抽象类不仅可以包含抽象方法,还可以包含完全实现的普通方法:
- 普通方法可以直接被子类继承使用
- 子类可以选择重写普通方法(使用
@Override) - 这是抽象类与接口的重要区别之一
java复制abstract class Animal {
// 抽象方法
public abstract void move();
// 普通方法
public void breathe() {
System.out.println("呼吸中...");
}
}
class Fish extends Animal {
@Override
public void move() {
System.out.println("游动");
}
// 可以选择不重写breathe()方法
}
3.3 抽象类的继承规则
关于抽象类的继承,有以下重要规则:
- 如果一个类包含抽象方法,那么它必须是抽象类
- 子类必须实现父类的所有抽象方法,否则子类也必须声明为抽象类
- 抽象类可以继承另一个抽象类,此时可以不实现父类的抽象方法
java复制abstract class Animal {
public abstract void eat();
}
// 子类不实现eat(),必须声明为抽象
abstract class Bird extends Animal {
public abstract void fly();
}
// 必须实现所有继承的抽象方法
class Sparrow extends Bird {
@Override
public void eat() {
System.out.println("吃虫子");
}
@Override
public void fly() {
System.out.println("飞翔");
}
}
4. 抽象类的典型应用场景
4.1 模板方法模式
抽象类特别适合实现模板方法模式,即:
- 在抽象类中定义算法的骨架
- 将某些步骤延迟到子类中实现
- 子类可以不改变算法结构的情况下重定义某些步骤
java复制abstract class DataProcessor {
// 模板方法(定义算法骨架)
public final void process() {
readData();
transformData();
saveData();
}
// 抽象步骤
protected abstract void transformData();
// 默认实现
protected void readData() {
System.out.println("读取数据...");
}
protected void saveData() {
System.out.println("保存数据...");
}
}
class CSVProcessor extends DataProcessor {
@Override
protected void transformData() {
System.out.println("转换CSV数据");
}
}
class XMLProcessor extends DataProcessor {
@Override
protected void transformData() {
System.out.println("转换XML数据");
}
@Override
protected void readData() {
System.out.println("读取XML数据...");
}
}
4.2 框架设计中的抽象类
在框架设计中,抽象类常用于:
- 定义框架的核心行为
- 提供默认实现
- 保留扩展点供用户自定义
java复制// 简单的Web框架示例
abstract class Controller {
// 抽象方法:处理请求
public abstract void handleRequest(HttpRequest request);
// 公共方法
protected void render(String viewName) {
System.out.println("渲染视图: " + viewName);
}
protected void redirect(String url) {
System.out.println("重定向到: " + url);
}
}
// 用户自定义控制器
class UserController extends Controller {
@Override
public void handleRequest(HttpRequest request) {
if (request.getPath().equals("/login")) {
// 处理登录逻辑
render("login");
} else {
redirect("/home");
}
}
}
4.3 抽象类与多态的结合
抽象类与多态是天生的搭档:
- 抽象类定义公共接口
- 子类提供具体实现
- 客户端代码通过父类引用操作子类对象
java复制abstract class Shape {
public abstract double area();
public abstract double perimeter();
}
class Circle extends Shape {
private double radius;
public Circle(double r) { radius = r; }
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
class Rectangle extends Shape {
private double width, height;
public Rectangle(double w, double h) {
width = w; height = h;
}
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}
// 使用多态处理不同形状
public class ShapeTest {
public static void printShapeInfo(Shape shape) {
System.out.println("面积: " + shape.area());
System.out.println("周长: " + shape.perimeter());
}
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rect = new Rectangle(4, 6);
printShapeInfo(circle);
printShapeInfo(rect);
}
}
5. 抽象类的设计考量与最佳实践
5.1 何时使用抽象类
在以下场景考虑使用抽象类:
- 多个相关类需要共享代码
- 需要定义子类必须实现的契约
- 需要控制子类的扩展方式
- 需要提供部分实现,同时保留扩展点
5.2 抽象类设计原则
设计良好的抽象类应遵循以下原则:
- 单一职责原则:一个抽象类应该只负责一个主要功能
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:子类应该能够替换父类而不破坏程序
- 依赖倒置原则:依赖抽象而非具体实现
5.3 抽象类与接口的选择
抽象类和接口都是实现抽象的机制,选择时考虑:
| 考虑因素 | 抽象类 | 接口 |
|---|---|---|
| 多重继承 | Java不支持 | 一个类可实现多个接口 |
| 状态/字段 | 可以包含实例字段 | 只能包含静态常量 |
| 方法实现 | 可以提供方法实现 | Java 8前不能有方法实现 |
| 设计目的 | 表示"是什么"的关系 | 表示"能做什么"的能力 |
| 扩展性 | 相对不易扩展 | 更容易扩展 |
| 版本兼容性 | 添加新方法可能破坏现有子类 | 默认方法保持向后兼容 |
5.4 抽象类的常见误用
避免以下抽象类的误用模式:
- 过度抽象:创建不必要的抽象层次
- 抽象泄漏:抽象类的实现细节泄漏到子类
- 巨型抽象类:包含太多不相关功能
- 违反LSP:子类不能完全替代父类
6. 抽象类在实际项目中的应用案例
6.1 Java集合框架中的抽象类
Java集合框架大量使用抽象类:
java复制// java.util.AbstractList
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
// 抽象方法
public abstract E get(int index);
// 提供部分实现
public E set(int index, E element) {
throw new UnsupportedOperationException();
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
// 其他实现...
}
// ArrayList继承AbstractList
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable {
@Override
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
// 实现其他方法...
}
6.2 Android中的抽象类应用
Android框架中广泛使用抽象类:
java复制// AsyncTask是Android中常用的抽象类
public abstract class AsyncTask<Params, Progress, Result> {
// 抽象方法 - 必须在子类中实现
protected abstract Result doInBackground(Params... params);
// 可选覆盖的方法
protected void onPreExecute() {}
protected void onPostExecute(Result result) {}
protected void onProgressUpdate(Progress... values) {}
// 其他实现...
}
// 使用示例
class DownloadTask extends AsyncTask<String, Integer, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) {
// 在后台线程执行下载
return downloadImage(urls[0]);
}
@Override
protected void onPostExecute(Bitmap result) {
// 在主线程更新UI
imageView.setImageBitmap(result);
}
}
6.3 游戏开发中的抽象类
游戏开发中常用抽象类定义游戏对象:
java复制abstract class GameObject {
protected float x, y;
protected int width, height;
public GameObject(float x, float y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
// 抽象方法
public abstract void update();
public abstract void render();
// 碰撞检测
public boolean collidesWith(GameObject other) {
return x < other.x + other.width &&
x + width > other.x &&
y < other.y + other.height &&
y + height > other.y;
}
}
class Player extends GameObject {
public Player(float x, float y) {
super(x, y, 32, 32);
}
@Override
public void update() {
// 处理玩家输入和移动
}
@Override
public void render() {
// 绘制玩家角色
}
}
class Enemy extends GameObject {
public Enemy(float x, float y) {
super(x, y, 32, 32);
}
@Override
public void update() {
// AI逻辑
}
@Override
public void render() {
// 绘制敌人
}
}
7. 抽象类的高级特性与技巧
7.1 抽象类的静态成员
抽象类可以包含静态成员(字段和方法):
java复制abstract class Logger {
// 静态字段
protected static int logCount = 0;
// 抽象方法
public abstract void log(String message);
// 静态方法
public static void resetLogCount() {
logCount = 0;
}
}
class FileLogger extends Logger {
@Override
public void log(String message) {
logCount++;
System.out.println("写入文件: " + message);
}
}
class ConsoleLogger extends Logger {
@Override
public void log(String message) {
logCount++;
System.out.println("控制台输出: " + message);
}
}
// 使用示例
Logger logger = new FileLogger();
logger.log("测试消息");
System.out.println("日志计数: " + Logger.logCount); // 1
Logger.resetLogCount();
7.2 抽象类的访问控制
抽象类和方法可以使用各种访问修饰符:
- 抽象类本身可以是public、protected或包私有
- 抽象方法可以使用public、protected
- 不能使用private(因为子类需要实现)
java复制public abstract class Vehicle {
// 公共抽象方法
public abstract void start();
// 受保护的抽象方法
protected abstract void stop();
// 错误:private abstract void turn(); // 编译错误
}
class Car extends Vehicle {
@Override
public void start() {
System.out.println("汽车启动");
}
@Override
protected void stop() {
System.out.println("汽车停止");
}
}
7.3 抽象类与泛型的结合
抽象类可以与泛型结合,创建类型安全的抽象结构:
java复制abstract class Repository<T> {
public abstract void save(T entity);
public abstract T findById(int id);
public abstract List<T> findAll();
}
class UserRepository extends Repository<User> {
@Override
public void save(User user) {
// 实现用户保存逻辑
}
@Override
public User findById(int id) {
// 实现查询逻辑
return null;
}
@Override
public List<User> findAll() {
// 实现查询所有用户
return null;
}
}
class ProductRepository extends Repository<Product> {
// 类似实现...
}
7.4 抽象类的设计模式应用
抽象类在许多设计模式中扮演重要角色:
- 模板方法模式:定义算法骨架
- 工厂方法模式:定义创建对象的接口
- 策略模式:定义算法族
- 装饰器模式:定义装饰器基类
java复制// 模板方法模式示例
abstract class Beverage {
// 模板方法
public final void prepare() {
boilWater();
brew();
pourInCup();
addCondiments();
}
protected abstract void brew();
protected abstract void addCondiments();
private void boilWater() {
System.out.println("烧水");
}
private void pourInCup() {
System.out.println("倒入杯子");
}
}
class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("冲泡咖啡");
}
@Override
protected void addCondiments() {
System.out.println("加糖和牛奶");
}
}
class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("浸泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("加柠檬");
}
}
8. 抽象类的常见问题与解决方案
8.1 抽象类与构造方法的陷阱
常见误区:认为抽象类不能有构造方法
解决方案:
- 抽象类可以有构造方法
- 构造方法用于初始化抽象类的状态
- 子类通过super()调用父类构造
java复制abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Dog extends Animal {
public Dog(String name) {
super(name); // 必须调用父类构造
}
}
8.2 抽象方法声明中的常见错误
常见错误:
- 忘记abstract关键字
- 提供方法体
- 非抽象类中包含抽象方法
解决方案:
java复制// 正确
abstract class A {
public abstract void method();
}
// 错误1:缺少abstract
class B {
public void method(); // 编译错误
}
// 错误2:提供方法体
abstract class C {
public abstract void method() {} // 编译错误
}
// 错误3:非抽象类包含抽象方法
class D {
public abstract void method(); // 编译错误
}
8.3 抽象类继承中的方法冲突
当抽象类继承另一个抽象类时,可能出现方法冲突:
java复制abstract class A {
public abstract void method();
}
abstract class B extends A {
public abstract void method(int param); // 重载,不是重写
}
class C extends B {
// 必须实现A.method()和B.method(int)
@Override
public void method() {}
@Override
public void method(int param) {}
}
8.4 抽象类与接口的混合使用
在实际开发中,常常同时使用抽象类和接口:
java复制interface Flyable {
void fly();
}
abstract class Bird {
public abstract void sing();
}
class Sparrow extends Bird implements Flyable {
@Override
public void sing() {
System.out.println("叽叽喳喳");
}
@Override
public void fly() {
System.out.println("飞翔");
}
}
9. 抽象类的性能考量
9.1 抽象类的方法调用开销
抽象方法调用与普通方法调用在性能上几乎没有差别:
- JVM使用虚方法表(vtable)实现动态绑定
- 抽象方法调用与接口方法调用机制类似
- 现代JVM优化使得这种开销可以忽略不计
9.2 抽象类与内存占用
抽象类对内存的影响:
- 每个类(包括抽象类)在JVM中有对应的Class对象
- 抽象类本身不增加实例内存开销
- 子类实例包含父类字段,与普通继承相同
9.3 设计时的性能考量
在设计抽象类时应考虑:
- 避免过深的继承层次(通常不超过3层)
- 将频繁调用的方法设计为非抽象方法
- 考虑使用final修饰不希望被重写的方法
java复制abstract class Shape {
// 频繁调用的方法,设计为非抽象
public final boolean isVisible() {
return true;
}
public abstract void draw();
}
10. 抽象类的演进与兼容性
10.1 向抽象类添加新方法
在抽象类演进过程中,添加新方法需要注意:
- 添加普通方法:可能破坏现有子类
- 添加抽象方法:强制所有子类实现,破坏性更大
- Java 8的默认方法(仅适用于接口)可以缓解这个问题
10.2 抽象类的版本控制策略
为了保持兼容性:
- 尽量通过添加而非修改来演进
- 考虑提供适配器抽象类
- 使用@Deprecated标记将被移除的方法
java复制abstract class LegacyProcessor {
@Deprecated
public abstract void process();
public void newProcess() {
process(); // 默认调用旧方法
}
}
abstract class NewProcessor extends LegacyProcessor {
@Override
public abstract void newProcess();
@Override
public void process() {
newProcess(); // 反向适配
}
}
10.3 抽象类与Java新特性
Java新版本对抽象类的影响:
- Java 8:接口支持默认方法,减少了抽象类的优势
- Java 9:接口支持私有方法,进一步缩小差异
- Java 15:密封类(sealed class)可以限制抽象类的继承
java复制// Java 15密封类示例
public abstract sealed class Shape
permits Circle, Rectangle, Triangle {
// ...
}
final class Circle extends Shape { /*...*/ }
final class Rectangle extends Shape { /*...*/ }
final class Triangle extends Shape { /*...*/ }
11. 抽象类的最佳实践总结
11.1 何时选择抽象类而非接口
选择抽象类当:
- 需要在多个相关类间共享代码
- 需要定义非静态/非final的字段
- 需要定义公共状态和行为
- 需要控制子类的构造方式
11.2 抽象类设计原则回顾
- 单一职责:一个抽象类应该只有一个改变的理由
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换:子类应该能够替换父类
- 依赖倒置:依赖抽象而非具体实现
- 接口隔离:客户端不应被迫依赖它们不用的方法
11.3 抽象类的命名约定
良好的抽象类命名习惯:
- 使用"Abstract"前缀:
AbstractList,AbstractButton - 使用"Base"后缀:
ViewBase,ServiceBase - 使用描述性名词:
Shape,Animal,Logger - 避免使用"I"前缀(这是接口的约定)
11.4 抽象类的文档规范
为抽象类编写文档时应:
- 明确说明类的设计目的
- 详细描述每个抽象方法的契约
- 提供典型用法的代码示例
- 说明子类实现的注意事项
java复制/**
* 表示图形对象的抽象基类。
*
* <p>子类必须实现{@link #draw()}方法来定义具体的绘制逻辑,
* 可以选择性重写{@link #getArea()}提供更高效的实现。
*
* @author 开发者
* @version 1.0
*/
public abstract class Shape {
/**
* 绘制图形。
*
* <p>子类必须实现此方法以定义具体的绘制逻辑。
*/
public abstract void draw();
/**
* 计算图形面积。
*
* <p>默认实现返回0。子类应该重写此方法以提供正确的面积计算。
*
* @return 图形的面积
*/
public double getArea() {
return 0;
}
}
12. 从抽象类到接口的演进思考
12.1 Java 8对抽象类的影响
Java 8引入接口的默认方法后,抽象类的使用场景有所变化:
- 接口现在可以包含方法实现(默认方法)
- 类可以实现多个接口,但只能继承一个抽象类
- 接口仍然不能包含实例字段
12.2 抽象类与接口的现代选择
在现代Java开发中:
- 优先考虑接口:当需要定义类型契约时
- 使用抽象类:当需要共享代码或定义公共状态时
- 组合使用:接口定义类型,抽象类提供部分实现
java复制// 接口定义契约
interface Drawable {
void draw();
}
// 抽象类提供部分实现
abstract class AbstractShape implements Drawable {
protected Color color;
protected AbstractShape(Color color) {
this.color = color;
}
public void setColor(Color color) {
this.color = color;
}
}
// 具体实现
class Circle extends AbstractShape {
private double radius;
public Circle(double radius, Color color) {
super(color);
this.radius = radius;
}
@Override
public void draw() {
System.out.printf("绘制半径为%.2f的%s圆形%n",
radius, color);
}
}
12.3 面向未来的抽象类设计
在设计抽象类时应考虑:
- 保持抽象类的小而专注
- 考虑将来可能被接口替代的可能性
- 使用组合而非深层次继承
- 考虑使用Java 15的密封类限制继承
java复制// 使用密封类限制抽象类的继承
public abstract sealed class PaymentProcessor
permits CreditCardProcessor, PayPalProcessor {
public abstract void processPayment(double amount);
}
final class CreditCardProcessor extends PaymentProcessor {
@Override
public void processPayment(double amount) {
// 信用卡处理逻辑
}
}
final class PayPalProcessor extends PaymentProcessor {
@Override
public void processPayment(double amount) {
// PayPal处理逻辑
}
}
13. 抽象类在领域驱动设计中的应用
13.1 DDD中的抽象基类
在领域驱动设计中,抽象类常用于:
- 定义领域模型的基类
- 封装通用领域逻辑
- 实现领域模式(如策略、规约)
java复制// 领域基类示例
public abstract class Entity<T> {
private T id;
protected Entity(T id) {
this.id = id;
}
public T getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Entity)) return false;
Entity<?> entity = (Entity<?>) o;
return id.equals(entity.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
// 具体领域实体
public class User extends Entity<Long> {
private String username;
public User(Long id, String username) {
super(id);
this.username = username;
}
// 其他领域逻辑...
}
13.2 抽象类与领域模式
抽象类可以实现多种领域模式:
- 策略模式:定义算法族
- 模板方法模式:定义操作骨架
- 规约模式:定义业务规则
java复制// 规约模式示例
public abstract class Specification<T> {
public abstract boolean isSatisfiedBy(T candidate);
public Specification<T> and(Specification<T> other) {
return new AndSpecification<>(this, other);
}
// 其他组合方法...
}
class AndSpecification<T> extends Specification<T> {
private Specification<T> left;
private Specification<T> right;
public AndSpecification(Specification<T> left, Specification<T> right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfiedBy(T candidate) {
return left.isSatisfiedBy(candidate) &&
right.isSatisfiedBy(candidate);
}
}
// 使用示例
Specification<User> isAdult = new Specification<>() {
@Override
public boolean isSatisfiedBy(User user) {
return user.getAge() >= 18;
}
};
Specification<User> isActive = new Specification<>() {
@Override
public boolean isSatisfiedBy(User user) {
return user.isActive();
}
};
Specification<User> isAdultAndActive = isAdult.and(isActive);
14. 抽象类在测试中的应用
14.1 测试基类的抽象实现
抽象类可用于创建测试基类:
- 定义测试的公共设置和清理逻辑
- 提供抽象方法让子类实现具体测试
- 实现模板测试模式
java复制public abstract class DatabaseTestBase {
protected DatabaseConnection connection;
@BeforeEach
public void setUp() {
connection = createConnection();
initializeDatabase();
}
@AfterEach
public void tearDown() {
cleanupDatabase();
connection.close();
}
protected abstract DatabaseConnection createConnection();
protected void initializeDatabase() {
// 默认实现
}
protected void cleanupDatabase() {
// 默认实现
}
@Test
public void testConnectionIsOpen() {
assertTrue(connection.isOpen());
}
}
class MySQLTest extends DatabaseTestBase {
@Override
protected DatabaseConnection createConnection() {
return new MySQLConnection();
}
@Test
public void testMySQLSpecificFeature() {
// MySQL特定测试
}
}
14.2 模拟对象的抽象基类
抽象类可用于创建模拟对象:
java复制public abstract class MockHttpServletResponse implements HttpServletResponse {
private String contentType;
private int status;
@Override
public void setContentType(String type) {
this.contentType = type;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public void setStatus(int sc) {
this.status = sc;
}
@Override
public int getStatus() {
return status;
}
// 其他方法的默认实现...
}
class TestHttpServletResponse extends MockHttpServletResponse {
// 可以只重写需要测试的方法
@Override
public void sendError(int sc, String msg) throws IOException {
throw new IOException("模拟错误: " + msg);
}
}
15. 抽象类的反模式与陷阱
15.1 抽象类的常见误用
- 过度使用抽象类:不是所有类层次都需要抽象类
- 巨型抽象类:包含太多不相关功能
- 抽象泄漏:抽象类的实现细节泄漏到子类
- 违反LSP:子类不能完全替代父类
15.2 抽象类的设计陷阱
避免以下设计陷阱:
- 过度继承:过深的继承层次难以维护
- 脆弱基类问题:基类修改影响所有子类
- 钻石继承问题:Java不支持多重继承
- 过早抽象:在需求不明确时创建抽象类
15.3 重构不良抽象类
重构不良抽象类的策略:
- 提取接口:将抽象类中的抽象方法提取为接口
- 组合替代继承:使用组合将功能分解
- 拆分类:将大抽象类拆分为多个小类
- 使用委托:将部分功能委托给其他类
java复制// 重构前:大抽象类
abstract class Worker {
public abstract void work();
public abstract void report();
public abstract void logHours();
// 许多其他方法...
}
// 重构后:使用组合
interface Workable {
void work();
}
interface Reportable {
void report();
}
class TimeLogger {
public void logHours() { /*...*/ }
}
class Employee implements Workable, Reportable {
private TimeLogger logger;
public Employee(TimeLogger logger) {
this.logger = logger;
}
@Override
public void work() { /*...*/ }
@Override
public void report() { /*...*/ }
public void logHours() {
logger.logHours();
}
}
16. 抽象类与其他语言的对比
16.1 Java与C++的抽象类比较
| 特性 | Java抽象类 | C++抽象类 |
|---|---|---|
| 关键字 | abstract | 无关键字,包含纯虚函数(=0) |
| 多重继承 | 不支持 | 支持 |
| 构造方法 | 可以有 | 可以有 |
| 纯虚函数/抽象方法 | abstract void method(); | virtual void method() = 0; |
| 实例化 | 不能直接实例化 | 不能直接实例化 |
16.2 Java与C#的抽象类比较
| 特性 | Java抽象类 | C#抽象类 |
|---|---|---|
| 关键字 | abstract | abstract |
| 密封类 | Java 15引入sealed | 有sealed关键字 |
| 默认方法 | 不支持(接口支持) | 支持 |
| 静态抽象方法 | 不支持 | C# 11支持 |
16.3 Java与Python的抽象类比较
| 特性 | Java抽象类 | Python抽象类 |
|---|---|---|
| 定义方式 | abstract关键字 | 继承abc.ABC或使用@abstractmethod |
| 强制实现 | 编译时检查 | 运行时检查 |
| 多重继承 | 不支持 | 支持 |
| 抽象属性 | 不支持 | 支持 |
python复制# Python抽象类示例
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "汪汪汪"
17. 抽象类的历史与演变
17.1 Java 1.0中的抽象类
Java最初版本中:
- 抽象类已经是语言核心特性
- 主要用于框架设计和类库实现
- 与接口形成互补关系