在Java的面向对象世界中,有两个常见但是经常被混淆的关键词:抽象类和接口。 很多初学者一脸问号:“这俩不都不能实例化、都可以定义抽象方法、都能被继承/实现吗?到底有什么区别?我啥时候用哪个?”
今天,我们就来系统梳理二者的本质区别、底层特性与使用场景。
一、抽象类
抽象类不是完整的类,它是给子类提供通用模版的基类。
抽象类里可以包含这些东西:
普通成员变量(字段)
构造方法
普通方法(带方法体)
抽象方法(没有方法体)
package com.lazy.snail.day14; /** * @ClassName AbstractAnimal * @Description TODO * @Author lazysnail * @Date 2025/5/28 13:46 * @Version 1.0 */ public abstract class AbstractAnimal { String name; public AbstractAnimal() { } public void breathe() { System.out.println("呼吸空气"); } public abstract void shout(); }
AbstractAnimal是一个abstract关键字修饰的类,表示这是一个抽象类。
这个类中包含一个普通成员变量name。
一个无参构造方法AbstractAnimal()。
一个普通方法breathe(),有方法体(大括号)。
一个抽象方法shout(),没有方法体(没有大括号)。
抽象类不能被实例化(不能通过new创建对象)。

如果有子类继承了抽象类,必须实现抽象类的抽象方法:

当Dog类继承了抽象类AbstractAnimal时,IDE会给出提示:
Dog要么也声明成抽象的(Dog也用abstract关键字修饰)。
要么就实现抽象类AbstractAnimal中的shout抽象方法。
抽象类适合用来描述“是什么”(Dog是一种Animal)
JDK中部分典型的抽象类:
java.io.InputStream
java.util.AbstractList
java.lang.Number
它们抽象了一类对象的本质属性(比如“输入流”“列表”“数值”),而不是具体实现。
二、接口
接口相对于抽象类更加的纯粹,它强调的是能力,而不是身份。
接口里定义的方法,默认是public abstract的,用来强制实现类具备某些功能。
package com.lazy.snail.day14; /** * @ClassName Flyable * @Description TODO * @Author lazysnail * @Date 2025/5/28 14:14 * @Version 1.0 */ public interface Flyable { void fly(); }
Flyable是自定义的一个接口,fly()是一个没有方法体的抽象方法,等着实现类实现具体的细节。

当Bird类通过implements关键字实现了Flyable接口,意味着Bird会飞,具体怎么飞由Bird中的fly方法决定。
使用implements关键字实现接口时,IDE会提示:
要么Bird类也声明成抽象的(abstarct),要么就要实现Flyable接口中的抽象方法fly。
package com.lazy.snail.day14; /** * @ClassName Bird * @Description TODO * @Author lazysnail * @Date 2025/5/28 14:15 * @Version 1.0 */ public class Bird implements Flyable{ @Override public void fly() { System.out.println("小鸟扑腾翅膀飞"); } }
接口适合描述“能做什么”(Bird能飞)
JDK中部分典型的接口:
java.util.List
java.lang.Runnable
java.util.Iterator
接口只声明方法签名,不提供任何实现,完全面向能力定义。
它们定义了一类对象对外暴露的行为能力,而不关注具体实现细节或继承关系。
三、接口的演化
在Java 8之前,接口只能包含抽象方法。但从Java 8开始,接口变得更强大:
可以定义default方法(有方法体)。
可以定义static静态方法。
但是仍然不能定义实例变量、构造方法。
package com.lazy.snail.day14; /** * @ClassName Moveable * @Description TODO * @Author lazysnail * @Date 2025/5/28 14:38 * @Version 1.0 */ public interface Moveable { default void move() { System.out.println("我能动"); } static void info() { System.out.println("接口的静态方法"); } }
Moveable是interface定义的接口,包含了一个default修饰的默认方法move、一个static修饰的静态info方法。
package com.lazy.snail.day14; /** * @ClassName Robot * @Description TODO * @Author lazysnail * @Date 2025/5/28 14:39 * @Version 1.0 */ public class Robot implements Moveable { @Override public void move() { System.out.println("机器人晃晃荡荡的走起来"); } }
Robot类通过implements实现了Moveable接口。
move方法的重写是可选的(可以不重写)。
为什么Java 8之后要引入这些变动?那必然是之前的设计有了局限性,不适应后续Java的发展了。
在Java 8之前,接口一旦发布,新增方法会强制要求所有实现类必须实现它,否则编译失败。这对已有代码库的兼容性是灾难性的。
Java 8引入Lambda表达式和Stream API,需要接口能够灵活扩展以支持新操作。
接口可以自行定义静态工具方法,替代传统的工具类(比如Collections类)。
接口的演进在实际开发过程中最主要的感受就是“接口功能升级而不破坏已有实现,极大增强了接口的扩展性”。 Java 9+接口的私有方法和Java 17+的密封类(Sealed Class)后续再讨论
四、抽象类和接口差异对比
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 是否可实例化 | X | X |
| 是否可被继承 | 单继承 | √ 多实现 |
| 是否支持构造方法 | √支持 | X 不支持 |
| 是否可包含字段 | √普通字段 | √仅常量(public static final) |
| 是否可有普通方法 | √支持 | √支持(default 方法) |
| 多继承支持 | X 不支持 | √支持多个接口 |
| 设计定位 | 模板(骨架) | 行为规范(能力) |
| 推荐使用时机 | 表示“是什么” | 表示“能做什么” |
五、组合使用
虽然抽象类和接口存在差异,但是二者并不是互相排斥,而是常常协同工作。
来看下下面的例子:
package com.lazy.snail.day14; /** * @ClassName AbstractDuck * @Description TODO * @Author lazysnail * @Date 2025/5/28 15:15 * @Version 1.0 */ public abstract class AbstractDuck { public void swim() { System.out.println("鸭子扑哧大脚板游泳"); } public abstract void display(); }
鸭子一般都会游泳,而且鸭子游泳一般都差不多是扑哧大脚板,抽象鸭子直接写了游泳的普通方法swim。
display方法是抽象方法,主要表示各类鸭子(实现类)有不同的展示,具体的由实现类去定义。
package com.lazy.snail.day14; /** * @ClassName WildDuck * @Description TODO * @Author lazysnail * @Date 2025/5/28 15:18 * @Version 1.0 */ public class WildDuck extends AbstractDuck implements Flyable{ @Override public void display() { System.out.println("我是一只野鸭子"); } @Override public void fly() { System.out.println("野鸭子展翅高飞"); } }
WildDuck继承了AbstractDuck且实现了Flyable。
那么这个野鸭子就会游泳(继承自抽象类的共性),有自己的展示方式(重写抽象方法),野鸭子还有个飞翔的能力(实现了飞翔接口,重写了fly方法)。
这个案例组合使用了抽象类和接口,抽象类负责提供共有的行为骨架,接口定义灵活的能力契约。
比如你后续定义了Bird,但是鸟跟鸭子区别很大,没什么共性抽象,但是都具备有飞的能力。
那么Bird直接实现Flyable接口,则具备了飞翔的行为能力。
六、应用场景和注意点
下面整了抽象类和接口的一些应用场景,在实际的开发过程中可以进行参考(看源代码的时候也可参考):
| 业务场景 | 推荐机制 | 理由 |
|---|---|---|
| 定义多个类共有的逻辑和字段 | 抽象类 | 支持字段和构造器 |
| 让多个类具备相同行为(比如“可比较”、“可序列化”) | 接口 | 接口能被多个类实现 |
| 构建插件机制、策略模式等 | 接口 | 规范行为,更灵活 |
| 为框架层提供通用父类 | 抽象类 | 可封装默认行为与状态 |
再说两点注意点:
接口并不是高级抽象类,它是另一种设计思路:接口强调的是能力和规范,抽象类强调的是共性和骨架。
类可以继承一个抽象类,实现多个接口
public class Dog extends AbstractAnimal implements Jumpable, Moveable { ... }
在实际开发中,我们通常这样做:
用抽象类提供共享的代码、状态和结构(比如Controller父类)
用接口定义横向的能力模块(比如Serializable、Runnable、Comparable)
抽象类为骨架,接口定契约 抽象类重共性,接口重能力 共性行为用抽象类,多种能力用接口
结语
记住并理解下面这段话:
抽象类定义身份的骨骼,承载共性逻辑与状态传承; 接口规范能力的边界,实现灵活的行为契约组合。
当需要构建血脉相连的家族树时,选择抽象类; 当需要为不同血脉赋予相同能力时,拥抱接口。 在后续的设计中,抽象类作为根基,接口作为羽翼自由扩展。
1030

被折叠的 条评论
为什么被折叠?



