什么是抽象类?
抽象类是一种不能被实例化的类,你不能用 new 关键字来创建一个抽象类的对象,它的主要目的是作为基类(父类),被其他类继承,从而实现代码的重用和定义一种通用的“蓝图”或“模板”。

你可以把它想象成一个“不完整”的类,它包含了一些已经实现的功能,也包含了一些尚未实现、必须由子类来完成的功能。
为什么需要抽象类?
抽象类主要用于以下场景:
- 定义通用行为:当多个类具有一些共同的特征和行为时,可以将这些共同的行为提取到一个父类中,如果这个父类本身不能独立存在(因为它描述的是一个抽象概念),就可以将其声明为抽象类。
- 强制子类实现特定功能:当父类中的某个方法,其具体实现依赖于子类的具体特性时,父类可以不提供实现,而是强制要求所有子类必须重写(实现)这个方法,这确保了所有子类都具有这个行为,尽管实现方式可能不同。
- 实现代码复用:抽象类可以包含具体的方法(有方法体的方法)和成员变量,子类可以直接继承和使用它们,避免了重复编写代码。
如何定义抽象类?
使用 abstract 关键字来修饰一个类,这个类就成为抽象类。
// 使用 abstract 关键字定义抽象类
public abstract class Animal {
// ... 类内容
}
抽象类的主要特征
1 抽象方法
这是抽象类最核心的特征。

- 定义:使用
abstract关键字修饰的方法,称为抽象方法。 - 特点:
- 抽象方法没有方法体(即没有 和具体的实现代码)。
- 它以分号
- 抽象方法必须在抽象类中定义。
public abstract class Animal {
// 抽象方法:没有方法体
public abstract void makeSound();
}
2 包含具体方法
抽象类不仅可以有抽象方法,也可以有具体的方法(有完整实现的方法),子类会继承这些具体的方法。
public abstract class Animal {
// 具体方法:有方法体
public void eat() {
System.out.println("This animal is eating.");
}
// 抽象方法:没有方法体,需要子类实现
public abstract void makeSound();
}
3 不能被实例化
这是抽象类最基本的规定,你不能创建抽象类的对象。
// 错误!不能创建抽象类的实例 Animal myAnimal = new Animal(); // 编译会报错
4 构造方法
抽象类可以有构造方法,虽然你不能创建抽象类的对象,但抽象类的构造方法会在其子类创建对象时被调用(通过 super() 语句),这通常用于初始化抽象类中定义的成员变量。
public abstract class Animal {
private String name;
// 抽象类的构造方法
public Animal(String name) {
this.name = name;
System.out.println("Animal constructor called for " + name);
}
// ...
}
5 继承规则
-
子类必须实现抽象方法:如果一个非抽象的类继承了一个抽象类,那么这个子类必须实现(重写)父类中的所有抽象方法,否则,这个子类也必须被声明为
abstract。
(图片来源网络,侵删)// 正确:子类实现了所有抽象方法 public class Dog extends Animal { public Dog(String name) { super(name); } @Override public void makeSound() { System.out.println("Woof!"); } } // 错误:子类没有实现 makeSound(),所以它也必须是抽象的 // public class Cat extends Animal { // public Cat(String name) { // super(name); // } // // 编译错误:Cat 不是抽象的,并且未覆盖 Animal 中的抽象方法 makeSound() // } -
抽象类可以继承抽象类:如果一个抽象类继承自另一个抽象类,它不需要强制实现父类的所有抽象方法,它可以选择实现其中一部分,或者一个也不实现。
一个完整的例子
让我们用一个经典的“形状”例子来理解抽象类。
第1步:定义抽象类 Shape
Shape 是一个抽象概念,我们无法直接画一个“形状”,只能画具体的圆形、正方形等。Shape 应该是抽象的。
// Shape.java
public abstract class Shape {
// 成员变量
protected String color;
// 构造方法
public Shape(String color) {
this.color = color;
}
// 抽象方法:计算面积,具体实现由子类决定
public abstract double calculateArea();
// 具体方法:获取颜色,所有子类都可以直接使用
public String getColor() {
return color;
}
// 具体方法:显示信息
public void displayInfo() {
System.out.println("This is a " + color + " shape.");
}
}
第2步:创建具体的子类
现在我们创建 Circle 和 Rectangle,它们都是具体的形状,可以被实例化。
// Circle.java
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color); // 调用父类 Shape 的构造方法
this.radius = radius;
}
// 必须实现父类的抽象方法
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
// Circle 可以有自己的特有方法
public double getRadius() {
return radius;
}
}
// Rectangle.java
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
// 必须实现父类的抽象方法
@Override
public double calculateArea() {
return width * height;
}
}
第3步:测试和使用
// Main.java
public class Main {
public static void main(String[] args) {
// Shape shape = new Shape("Red"); // 错误!不能实例化抽象类
// 创建具体的子类对象
Circle circle = new Circle("Blue", 5.0);
Rectangle rectangle = new Rectangle("Green", 4.0, 6.0);
// 调用方法
System.out.println("--- Circle Info ---");
circle.displayInfo(); // 调用继承自 Shape 的具体方法
System.out.println("Area: " + circle.calculateArea()); // 调用子类实现的抽象方法
System.out.println("Radius: " + circle.getRadius()); // 调用子类自己的方法
System.out.println("\n--- Rectangle Info ---");
rectangle.displayInfo();
System.out.println("Area: " + rectangle.calculateArea());
}
}
输出结果:
--- Circle Info ---
This is a Blue shape.
Area: 78.53981633974483
Radius: 5.0
--- Rectangle Info ---
This is a Green shape.
Area: 24.0
抽象类 vs. 接口
这是初学者经常混淆的两个概念,它们都可以用来定义“契约”,但有显著区别。
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 继承 | 类只能继承一个抽象类(单继承) | 类可以实现多个接口(多实现) |
| 方法 | 可以包含抽象方法和具体方法 | 从 Java 8 开始,可以包含 default 和 static 方法,但主要还是用于定义抽象行为 |
| 成员变量 | 可以包含各种类型的成员变量(private, protected, public, static, final) |
成员变量默认是 public static final 的,即常量 |
| 构造方法 | 有构造方法 | 没有(构造方法) |
| 目的 | 提供一个“是什么”的关系(Dog 是一个 Animal),用于代码复用和建立类层次结构 |
提供一个“能做什么”的能力(Bird 能飞),用于定义行为规范 |
| 访问修饰符 | 方法可以是任何访问修饰符 | 方法默认是 public |
简单总结:
- 当你想描述一个“is-a”(是一个)的关系,并且希望子类能复用一些代码时,使用抽象类。
- 当你想定义一个类必须具备的“can-do”(能做)的能力,特别是当一个类需要从多个来源继承行为时,使用接口。
在现代 Java 开发中,更推荐优先使用接口,因为它提供了更大的灵活性(多实现),而抽象类则更适合用于构建稳固的类层次结构。
希望这个详细的解释能帮助你完全理解 Java 中的抽象类!
