Day14 | 抽象类和接口

在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)后续再讨论

四、抽象类和接口差异对比

特性抽象类接口
是否可实例化XX
是否可被继承单继承√ 多实现
是否支持构造方法√支持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)

抽象类为骨架,接口定契约 抽象类重共性,接口重能力 共性行为用抽象类,多种能力用接口

结语

记住并理解下面这段话:

抽象类定义身份的骨骼,承载共性逻辑与状态传承; 接口规范能力的边界,实现灵活的行为契约组合。

当需要构建血脉相连的家族树时,选择抽象类; 当需要为不同血脉赋予相同能力时,拥抱接口。 在后续的设计中,抽象类作为根基,接口作为羽翼自由扩展。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值