Java 抽象类与接口:核心概念、区别与最佳实践
Java 抽象类与接口
抽象类:在面向对象的概念中,一切皆为对象,所有的对象都是通过类来描绘的,但是相应的,并不是所有的类都是用来描绘对象的,如果一个类中没有足够的信息(变量和方法)来清晰具体的描绘对象,也就是说它是模糊的、抽象的,没法表述清楚现实的对象,而只能让子类完成抽象到具体的实现,那么这样的类称为抽象类。
接口:抽象类是从多个类中抽象出来的模板,但实际上它还不够抽象(还能有具体的方法),如果将这种抽象贯彻的更彻底,那便可以得到一种更加特殊的“抽象类”——接口(Interface)。接口是 Java 中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。
抽象类(Abstract Class)和接口(Interface)是Java实现抽象和多态的两大核心机制,也是面向对象设计的基石。两者都不能被实例化,但设计理念、语法规则和使用场景有本质区别,应用非常广泛。
一、抽象类(Abstract Class)
1. 定义与语法
用abstract关键字修饰的类称为抽象类,但更确切的说,如果一个类包含了抽象的方法(只包含方法的声明,而不包含方法体)则该类就必须定义成抽象类,相当于一旦有了抽象的行为(方法),就打上了抽象的标签,它是对一类事物的共性抽象,代表"是什么"(is-a)的关系。
public abstract class class_name { //声明抽象类
//声明抽象类成员
}
示例:
// 抽象类定义
public abstract class Animal {
// 成员变量
protected String name;
// 构造方法(抽象类有构造方法,供子类调用)
public Animal(String name) {
this.name = name;
}
// 抽象方法:只有声明,没有实现(用abstract修饰)
public abstract void makeSound();
// 非抽象方法:有具体实现,子类可直接继承或重写
public void eat() {
System.out.println(name + "在吃东西");
}
}
// 子类继承抽象类
public class Cat extends Animal {
public Cat(String name) {
super(name); // 必须调用父类构造方法
}
// 必须实现所有抽象方法(否则子类也必须声明为abstract)
@Override
public void makeSound() {
System.out.println(name + "喵喵叫");
}
}
2. 核心特点
- 不能实例化:只能作为父类被继承,无法通过
new创建对象!(对象的产生,它一切应该有的就必须是实在的,方法必须全有而且可实现,否则不完整,但抽象类包含抽象的方法,无法实现,想像一下机器人有手有脚有轮子但不能行动,那和模型有什么区别) - 可以包含:成员变量、构造方法、普通方法、抽象方法、静态方法、final方法。
- 继承规则:一个类只能继承一个抽象类(Java单继承限制)
- 抽象方法约束:子类必须实现父类所有抽象方法,否则子类自身也必须是抽象类
- 访问权限:抽象方法不能是
private、static、final(否则无法被重写)
二、接口(Interface)
1. 定义与语法
接口是对行为的抽象,代表"能做什么"(can-do)的关系,它所有的方法都是抽象的,他完全把程序的功能和实现分开来,本质是一套契约规范。虽然抽象类体现了方法重写的优势,但它的约束总归较小(还能有具体的方法),而接口的约束就很强,它强行让子类重新所有方法。而且Java的单继承结构是一个树状结构,但还是有一些局限,可以利用接口弥补这个不足,即一个子类可以实现多个接口
// 用interface关键字定义接口
interface Flyable {
// 接口里只能有抽象方法(JDK8之前)
// 抽象方法默认是public abstract,可以省略不写
void fly();
}
interface Swimmable {
void swim();
}
// 2. 类用implements实现接口
class Bird implements Flyable {
// 必须实现接口的所有抽象方法
@Override
public void fly() {
System.out.println("小鸟在天上飞");
}
}
// 一个类可以同时实现多个接口
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子在低空飞");
}
@Override
public void swim() {
System.out.println("鸭子在水里游");
}
}
// 测试
public class Test {
public static void main(String[] args) {
// Flyable f = new Flyable(); // 错误!接口不能直接new
Flyable bird = new Bird(); // 正确!用实现类创建对象
bird.fly(); // 输出:小鸟在天上飞
Duck duck = new Duck();
duck.fly(); // 输出:鸭子在低空飞
duck.swim(); // 输出:鸭子在水里游
}
}
2. 核心特点
- 不能实例化:只能被类实现(
implements)或被其他接口继承(extends) - 成员限制(JDK8前):只能有
public static final常量和public abstract抽象方法 - JDK8+增强:支持默认方法(
default)、静态方法(static) - JDK9增强:支持私有方法(
private)和私有静态方法 - 多实现支持:一个类可以同时实现多个接口(解决Java单继承的局限性)
- 继承规则:接口可以继承多个接口(
interface A extends B, C)
三、抽象类 vs 接口:核心区别对比
| 对比维度 | 抽象类 | 接口 |
|---|---|---|
| 定义关键字 | abstract class |
interface |
| 继承/实现方式 | 类用extends继承,单继承 |
类用implements实现,多实现;接口用extends继承,多继承 |
| 成员变量 | 任意访问权限,可修改 | 默认public static final,必须初始化,不可修改 |
| 构造方法 | 有构造方法(供子类调用) | 无构造方法 |
| 方法类型 | 抽象方法、普通方法、静态方法、final方法 | JDK8前:仅抽象方法 JDK8+:抽象方法、默认方法、静态方法 JDK9+:新增私有方法 |
| 访问权限 | 支持public、protected、default、private |
方法默认public,不能使用其他访问权限 |
| 设计理念 | 对类的抽象,提取共性代码,体现"is-a"关系 | 对行为的抽象,定义契约规范,体现"can-do"关系 |
| 代码复用 | 通过继承实现,复用父类的实现代码 | 通过实现接口实现,复用行为规范,不能复用实现(默认方法除外) |
四、使用场景与最佳实践
1. 什么时候用抽象类?
当多个类有共同的属性和方法,并且它们之间是 “是一个” 的关系时,用抽象类
例:猫、狗都是动物 → 抽象类 Animal
- 需要在多个相关类之间共享代码(提取公共方法和属性)
- 需要定义子类的模板行为(模板方法模式)
- 需要为子类提供默认实现
- 类之间存在明确的层次关系(is-a)
示例:模板方法模式
public abstract class Game {
// 模板方法:定义游戏流程(final防止子类重写流程)
public final void play() {
initialize();
startPlay();
endPlay();
}
protected abstract void initialize();
protected abstract void startPlay();
protected abstract void endPlay();
}
2. 什么时候用接口?
当多个类有共同的行为,但没有共同的属性,或者需要一个类有多种行为时,用接口
例:鸟、飞机都会飞 → 接口 Flyable
- 需要定义多个不相关类的共同行为
- 需要实现多态和解耦(依赖倒置原则)
- 需要定义系统的外部契约(如API、回调接口)
- 需要实现插件化架构(可插拔组件)
示例:回调接口
// 回调接口
public interface OnClickListener {
void onClick();
}
// 按钮类
public class Button {
private OnClickListener listener;
public void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
public void click() {
if (listener != null) {
listener.onClick(); // 触发回调
}
}
}
3. 组合使用:抽象类+接口
实际开发中,两者经常结合使用:
- 抽象类实现接口,提供接口的默认实现(适配器模式)
- 子类继承抽象类,同时实现其他接口
示例:适配器模式
// 接口
public interface Shape {
void draw();
void resize();
}
// 抽象适配器类:实现接口,提供空实现
public abstract class ShapeAdapter implements Shape {
@Override
public void draw() {}
@Override
public void resize() {}
}
// 子类只需重写需要的方法
public class Circle extends ShapeAdapter {
@Override
public void draw() {
System.out.println("画圆形");
}
}
五、常见误区
- 误区1:抽象类必须包含抽象方法
- 纠正:抽象类可以没有抽象方法(此时主要用于禁止实例化)
- 误区2:接口只能有抽象方法
- 纠正:JDK8+支持默认方法和静态方法,JDK9+支持私有方法
- 误区3:接口的默认方法和抽象类的普通方法没有区别
- 纠正:默认方法不能访问实例变量,而抽象类的普通方法可以;一个类可以实现多个接口的默认方法,但只能继承一个抽象类的普通方法
- 误区4:抽象类和接口都不能有静态方法
- 纠正:两者都可以有静态方法,接口的静态方法通过接口名调用,抽象类的静态方法通过类名调用
六、总结
- 抽象类是类的抽象,用于代码复用和定义模板,体现"is-a"关系
- 接口是行为的抽象,用于定义契约和实现多态,体现"can-do"关系
- 优先使用接口定义行为,抽象类用于实现代码复用
- 当需要同时复用代码和定义契约时,使用"抽象类实现接口+子类继承抽象类"的组合模式
更多推荐



所有评论(0)