在 Java 面向对象的学习中,抽象类是继封装、继承、多态之后的又一核心知识点,也是实现代码抽象和复用的重要手段,同时还是面试中的高频考点。本文将从抽象类的基本概念、定义语法、核心特性、使用场景到与接口的区别,全方位拆解 Java 抽象类,帮你彻底吃透这一知识点。
一、什么是抽象类?为什么需要抽象类?
在面向对象的设计中,我们会遇到这样一种情况:某个父类只定义了子类共同的行为特征,却无法具体实现该行为,因为不同子类的实现方式各不相同。
假设你的公司要进行招聘,招聘2名产品经理,10名程序员,现在让你和小A设计这个网站让报名人员填写基本信息。大概有两种设计方式—-。
小A的设计方法:

但是我们发现这两个页面基本上内容是相同的,于是你的设计方法是这样的:

我们将一些相同的信息都提取到一起我们把这个过程叫做—-抽象
抽象类的本质:是一种被抽象化的父类,是对多个具有共同特征的子类的抽象描述,它不能被实例化,只能作为子类的父类被继承,核心作用是规范子类的行为,同时实现代码的复用。
二、抽象类的定义语法
1. 抽象类的定义
使用abstract关键字修饰的类,就是抽象类,语法格式:
// 抽象类的定义
public abstract class 类名 {
// 类的成员:属性、普通方法、抽象方法等
}
2. 抽象方法的定义
使用abstract关键字修饰,只有方法声明,没有方法体(无{})的方法,就是抽象方法,语法格式:
// 抽象方法的定义,无方法体,以分号结尾
public abstract 返回值类型 方法名(参数列表);
3. 完整示例
以Animal抽象类为例,定义抽象方法eat()和普通方法breathe()(所有动物呼吸方式可统一实现):
// 抽象父类:Animal
public abstract class Animal {
// 普通属性
public String name;
// 普通方法:可直接实现,子类继承复用
public void breathe() {
System.out.println(name + "在呼吸");
}
// 抽象方法:只声明,由子类实现
public abstract void eat();
}
4. 抽象类的子类实现
抽象类必须被子类继承,子类需重写抽象类中所有的抽象方法(除非子类也是抽象类),否则编译报错。
// 子类Cat继承抽象类Animal
public class Cat extends Animal {
// 重写抽象方法eat(),实现具体逻辑
@Override
public void eat() {
name = "小猫";
System.out.println(name + "吃小鱼干");
}
}
// 子类Dog继承抽象类Animal
public class Dog extends Animal {
@Override
public void eat() {
name = "小狗";
System.out.println(name + "啃骨头");
}
}
5. 测试类
抽象类不能实例化(new Animal()会编译报错),但可以声明抽象类的引用,指向子类的实例(多态特性):
public class TestAnimal {
public static void main(String[] args) {
// 抽象类不能实例化
// Animal animal = new Animal(); // 编译报错
// 多态:抽象类引用指向子类实例
Animal cat = new Cat();
cat.eat(); // 输出:小猫吃小鱼干
cat.breathe(); // 输出:小猫在呼吸
Animal dog = new Dog();
dog.eat(); // 输出:小狗啃骨头
dog.breathe(); // 输出:小狗在呼吸
}
}
三、抽象类的核心特性
抽象类是特殊的类,既拥有普通类的特性,又有自身的特殊规则,这是学习的重点,也是易混点,核心特性总结如下:
1. 抽象类不能被实例化
这是抽象类最核心的特性,new 抽象类名()会直接编译报错,因为抽象类是 “不完整的类”,包含未实现的抽象方法,无法创建具体对象。
2. 抽象类可以包含多种成员
抽象类并非只有抽象方法,它可以包含普通类的所有成员:
- 普通属性:如String name、int age;
- 普通方法:有方法体,子类可直接继承复用,无需重写;
- 抽象方法:无方法体,要求子类必须重写(子类非抽象类时);
- 构造方法:抽象类有构造方法,用于子类继承时初始化父类的属性,子类通过super()调用;
- 静态成员:静态属性和静态方法,可通过抽象类名.静态成员直接调用。
示例:含构造方法的抽象类
public abstract class Animal {
public String name;
// 构造方法
public Animal(String name) {
this.name = name;
}
public abstract void eat();
}
// 子类通过super调用父类构造方法
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name + "吃小鱼干");
}
}
// 测试
Animal cat = new Cat("橘猫");
cat.eat(); // 输出:橘猫吃小鱼干
3. 子类必须重写抽象类的所有抽象方法
如果子类不是抽象类,就必须重写抽象父类中所有的抽象方法,缺一不可,否则编译器会直接报错;若子类也是抽象类,则可以不重写,由该抽象子类的子类去完成重写。
4. 抽象类可以被继承,支持单继承
抽象类的继承规则与普通类一致,Java 中类只支持单继承,即一个抽象类只能有一个直接父类,同时抽象类也可以继承普通类或另一个抽象类。
5. abstract关键字的使用限制
abstract关键字不能与以下关键字同时使用,否则编译报错:
- final:final 类不能被继承,而抽象类必须被继承,二者冲突;
- private:private 方法无法被子类访问,而抽象方法要求子类重写,二者冲突;
- static:static 方法属于类,不依赖对象,而抽象方法需要子类实现,属于对象层面,二者冲突;
- native:native 方法由本地代码实现,抽象方法无实现,二者冲突。
四、抽象类的使用场景
抽象类的设计符合面向对象的 “里氏替换原则”和“开闭原则”,在实际开发中,以下场景优先使用抽象类:
1. 定义多个子类的共同行为规范
当多个子类具有相同的方法声明,但实现逻辑不同时,将共同方法声明为抽象方法,放在抽象父类中,规范子类的行为,保证代码的一致性。比如:图形类(Circle、Rectangle、Triangle)都有getArea()(求面积)方法,抽象父类Shape定义抽象方法getArea(),子类各自实现自身的面积计算逻辑。
2. 实现代码的复用
抽象类中可定义普通方法和属性,子类继承后可直接复用,无需重复编写,减少代码冗余。比如:所有手机(Phone)都有powerOn()(开机)、powerOff()(关机)的通用逻辑,放在抽象父类Phone中实现,子类(HuaWei、Apple、Xiaomi)只需重写call()(通话)等个性化方法。
3. 作为多态的父类,提高代码的扩展性
抽象类的引用可以指向任意子类的实例,结合多态特性,在方法传参时使用抽象类作为参数类型,可接收所有子类对象,无需为每个子类单独写方法,大大提高代码的扩展性和可维护性。
示例:多态提升扩展性
// 抽象类Animal
public abstract class Animal {
public abstract void eat();
}
// 子类Cat、Dog、Bird(略)
// 测试类:喂动物方法,参数为抽象类Animal
public class Test {
public static void feed(Animal animal) {
animal.eat();
}
public static void main(String[] args) {
feed(new Cat()); // 喂猫
feed(new Dog()); // 喂狗
feed(new Bird()); // 喂鸟
// 新增子类Tiger,无需修改feed方法,直接传入即可,符合开闭原则
feed(new Tiger());
}
}
五、抽象类与普通类的区别
很多初学者容易混淆抽象类和普通类,这里通过表格清晰对比二者的核心区别:
表格
| 关键字修饰 | 需用 abstract 修饰 | 无需 abstract 修饰 |
| 实例化 | 不能被实例化 | 可以被实例化(new) |
| 抽象方法 | 可以包含抽象方法 | 不能包含抽象方法 |
| 子类要求 | 子类非抽象时必须重写所有抽象方法 | 子类可自由重写父类方法 |
| 设计目的 | 规范子类行为 + 代码复用 | 描述具体对象的属性和行为 |
六、抽象类与接口的区别(高频面试题)
抽象类和接口都是 Java 中实现抽象化的方式,二者在功能上有相似之处,也是面试中必考的对比题,核心区别如下(重点记忆):
表格
| 关键字 | abstract class | interface |
| 继承方式 | 类单继承(extends) | 类多实现(implements),接口多继承 |
| 成员组成 | 可包含普通属性、抽象方法、普通方法、构造方法、静态成员 | JDK8 前:只有常量(public static final)和抽象方法;JDK8 后:新增默认方法、静态方法;JDK9 后:新增私有方法 |
| 方法修饰符 | 抽象方法默认 public,可省略;普通方法可任意修饰(除 private/final/static) | 所有方法默认 public abstract(JDK8 前);默认方法为 public default,静态方法为 public static |
| 属性修饰符 | 属性可任意修饰(public/private/protected 等) | 属性默认 public static final,必须初始化 |
| 构造方法 | 有构造方法 | 无构造方法 |
| 设计理念 | 体现继承关系,是对 “类” 的抽象,注重代码复用和行为规范 | 体现实现关系,是对 “行为” 的抽象,注重解耦和多实现 |
| 使用场景 | 多个子类有共同的属性和方法,需要代码复用 | 多个不相关的类有共同的行为,需要统一规范,实现多态 |





