首页 开发教程 Day14 | 抽象类和接口

Day14 | 抽象类和接口

开发教程 2025年12月4日
147 浏览

在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抽象方法。

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(\"小鸟扑腾翅膀飞\");
    }
}

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类)。

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

特性 抽象类 接口
是否可实例化 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)

结语

记住并理解下面这段话:

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

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

下一篇预告

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

更多文章请关注我的公众号《懒惰蜗牛工坊》

发表评论
暂无评论

还没有评论呢,快来抢沙发~

客服

点击联系客服 点击联系客服

在线时间:09:00-18:00

关注微信公众号

关注微信公众号
客服电话

400-888-8888

客服邮箱 122325244@qq.com

手机

扫描二维码

手机访问本站

扫描二维码
搜索