Java抽象类和接口区别_场景理解

Java 抽象类和接口的区别:从设计场景理解

核心一句话:

抽象类解决"我是谁"的问题,接口解决"我能做什么"的问题。


1. 为什么 Java 要设计抽象类和接口?

在 Java 开发中,经常会遇到这种情况:

  • 很多类有相同的属性和方法;
  • 很多类虽然不是同一种东西,但具备相同的能力;
  • 我们希望代码更清晰、更容易扩展、更少重复。

所以 Java 提供了两种抽象机制:

机制 主要解决的问题
抽象类 抽取同一类事物的共同特征
接口 抽取不同事物的共同能力

2. 抽象类解决什么场景问题?

2.1 适用场景

抽象类适合处理这种关系:

A 是一种 B

例如:

  • 狗是一种动物;
  • 猫是一种动物;
  • 老师是一种人;
  • 学生是一种人;
  • 微信支付是一种支付方式;
  • 支付宝支付也是一种支付方式。

这些对象属于同一大类,有共同属性,也有共同方法。


3. 不使用抽象类会有什么问题?

假设我们要写 DogCat 两个类。

java 复制代码
class Dog {
    String name;
    int age;

    public void sleep() {
        System.out.println("动物睡觉");
    }

    public void eat() {
        System.out.println("狗吃骨头");
    }
}

class Cat {
    String name;
    int age;

    public void sleep() {
        System.out.println("动物睡觉");
    }

    public void eat() {
        System.out.println("猫吃鱼");
    }
}

可以发现,DogCat 中有重复代码:

java 复制代码
String name;
int age;

public void sleep() {
    System.out.println("动物睡觉");
}

如果动物越来越多,比如 BirdTigerRabbit,重复代码会越来越多。

这时就可以使用抽象类。


4. 使用抽象类优化代码

4.1 定义抽象父类

java 复制代码
abstract class Animal {
    String name;
    int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sleep() {
        System.out.println(name + " 正在睡觉");
    }

    public abstract void eat();
}

这里有两个重点:

java 复制代码
public void sleep() {
    System.out.println(name + " 正在睡觉");
}

这是普通方法,所有动物都可以复用。

java 复制代码
public abstract void eat();

这是抽象方法。因为不同动物吃的东西不同,所以父类只规定"动物必须会吃",但不写具体实现。


4.2 子类继承抽象类

java 复制代码
class Dog extends Animal {

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(name + " 吃骨头");
    }
}
java 复制代码
class Cat extends Animal {

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(name + " 吃鱼");
    }
}

4.3 测试代码

java 复制代码
public class Test {
    public static void main(String[] args) {
        Animal dog = new Dog("旺财", 3);
        Animal cat = new Cat("小花", 2);

        dog.sleep();
        dog.eat();

        cat.sleep();
        cat.eat();
    }
}

输出结果:

text 复制代码
旺财 正在睡觉
旺财 吃骨头
小花 正在睡觉
小花 吃鱼

5. 抽象类到底解决了什么?

抽象类主要解决三个问题:

5.1 解决代码重复问题

公共属性和公共方法可以放到父类中。

java 复制代码
String name;
int age;
public void sleep() {}

这些内容不需要在每个子类里重复写。


5.2 解决统一规范问题

抽象方法可以强制子类实现。

java 复制代码
public abstract void eat();

只要继承了 Animal,就必须实现 eat() 方法。


5.3 解决多态调用问题

可以用父类类型接收子类对象。

java 复制代码
Animal animal = new Dog("旺财", 3);
animal.eat();

虽然变量类型是 Animal,但真正执行的是 Dog 中的 eat() 方法。


6. 接口解决什么场景问题?

6.1 适用场景

接口适合处理这种关系:

A 具备某种能力

例如:

  • 鸟可以飞;
  • 飞机可以飞;
  • 超人可以飞;
  • 汽车可以被驾驶;
  • 人也可以驾驶;
  • 打印机可以打印;
  • 订单也可以打印。

这些对象不一定属于同一类,但它们具备相同的能力。


7. 为什么这种场景不能只用抽象类?

假设我们想描述"会飞"的东西。

有这些对象:

  • 鸟;
  • 飞机;
  • 超人。

它们都能飞,但它们不是同一种东西:

text 复制代码
鸟 是 动物
飞机 是 交通工具
超人 是 人

如果强行设计一个父类:

java 复制代码
abstract class FlyingObject {
    public abstract void fly();
}

然后让 BirdAirplaneSuperman 都继承它,看似可以,但设计不合理。

因为飞机不是动物,鸟也不是交通工具,它们只是都具备"飞行能力"。

所以更适合用接口。


8. 使用接口描述能力

8.1 定义接口

java 复制代码
interface Flyable {
    void fly();
}

这个接口表示:

只要实现了 Flyable,就说明这个类具备飞行能力。


8.2 不同类实现接口

java 复制代码
class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("鸟扇动翅膀飞行");
    }
}
java 复制代码
class Airplane implements Flyable {
    @Override
    public void fly() {
        System.out.println("飞机依靠发动机飞行");
    }
}
java 复制代码
class Superman implements Flyable {
    @Override
    public void fly() {
        System.out.println("超人直接飞行");
    }
}

8.3 测试代码

java 复制代码
public class Test {
    public static void main(String[] args) {
        Flyable bird = new Bird();
        Flyable airplane = new Airplane();
        Flyable superman = new Superman();

        bird.fly();
        airplane.fly();
        superman.fly();
    }
}

输出结果:

text 复制代码
鸟扇动翅膀飞行
飞机依靠发动机飞行
超人直接飞行

9. 接口到底解决了什么?

接口主要解决三个问题:

9.1 解决能力抽象问题

接口不关心对象是什么,只关心对象能做什么。

java 复制代码
interface Flyable {
    void fly();
}

这表示所有实现类都具备飞行能力。


9.2 解决多实现问题

Java 中一个类只能继承一个父类,但可以实现多个接口。

例如,一个人既可以开车,也可以游泳。

java 复制代码
interface Drivable {
    void drive();
}

interface Swimmable {
    void swim();
}
java 复制代码
class Person implements Drivable, Swimmable {
    @Override
    public void drive() {
        System.out.println("人可以开车");
    }

    @Override
    public void swim() {
        System.out.println("人可以游泳");
    }
}

如果用抽象类,Java 不允许这样写:

java 复制代码
// 错误写法:Java 不支持类的多继承
class Person extends Driver, Swimmer {
}

但是接口可以:

java 复制代码
class Person implements Drivable, Swimmable {
}

9.3 解决程序扩展问题

假设我们写一个方法,让所有能飞的对象都飞起来:

java 复制代码
public static void makeItFly(Flyable obj) {
    obj.fly();
}

调用时:

java 复制代码
makeItFly(new Bird());
makeItFly(new Airplane());
makeItFly(new Superman());

以后如果新增一个 Drone 无人机类,只需要实现 Flyable 接口:

java 复制代码
class Drone implements Flyable {
    @Override
    public void fly() {
        System.out.println("无人机通过螺旋桨飞行");
    }
}

原来的方法不用修改:

java 复制代码
makeItFly(new Drone());

这就是接口带来的扩展性。


10. 抽象类和接口可以一起使用

在实际开发中,抽象类和接口经常一起使用。

例如:老师是一种人,同时老师也可以开车。

10.1 定义抽象类 Person

java 复制代码
abstract class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sleep() {
        System.out.println(name + " 需要睡觉");
    }

    public abstract void work();
}

10.2 定义接口 Drivable

java 复制代码
interface Drivable {
    void drive();
}

10.3 Teacher 继承抽象类,同时实现接口

java 复制代码
class Teacher extends Person implements Drivable {

    public Teacher(String name, int age) {
        super(name, age);
    }

    @Override
    public void work() {
        System.out.println(name + " 正在教学");
    }

    @Override
    public void drive() {
        System.out.println(name + " 开车去学校");
    }
}

10.4 测试代码

java 复制代码
public class Test {
    public static void main(String[] args) {
        Teacher teacher = new Teacher("张老师", 35);

        teacher.sleep();
        teacher.work();
        teacher.drive();
    }
}

输出结果:

text 复制代码
张老师 需要睡觉
张老师 正在教学
张老师 开车去学校

这个例子中:

java 复制代码
class Teacher extends Person

表示:

老师是一种人。

java 复制代码
class Teacher implements Drivable

表示:

老师具备开车能力。


11. 真实开发场景:支付系统

假设我们在开发一个订单支付系统,需要支持:

  • 微信支付;
  • 支付宝支付;
  • 银行卡支付。

不同支付方式的具体实现不同,但它们都有共同能力:

  • 支付;
  • 退款。

这时可以定义一个接口。


11.1 定义支付接口

java 复制代码
interface Payment {
    void pay(double amount);
    void refund(double amount);
}

11.2 微信支付实现接口

java 复制代码
class WeChatPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用微信支付:" + amount + " 元");
    }

    @Override
    public void refund(double amount) {
        System.out.println("微信退款:" + amount + " 元");
    }
}

11.3 支付宝支付实现接口

java 复制代码
class AliPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用支付宝支付:" + amount + " 元");
    }

    @Override
    public void refund(double amount) {
        System.out.println("支付宝退款:" + amount + " 元");
    }
}

11.4 订单服务只依赖接口

java 复制代码
class OrderService {
    public void checkout(Payment payment, double amount) {
        payment.pay(amount);
    }
}

11.5 测试代码

java 复制代码
public class Test {
    public static void main(String[] args) {
        OrderService orderService = new OrderService();

        Payment weChatPay = new WeChatPay();
        Payment aliPay = new AliPay();

        orderService.checkout(weChatPay, 100);
        orderService.checkout(aliPay, 200);
    }
}

输出结果:

text 复制代码
使用微信支付:100.0 元
使用支付宝支付:200.0 元

12. 为什么这个支付案例适合用接口?

因为订单服务并不关心具体是哪种支付方式。

它只关心一件事:

java 复制代码
payment.pay(amount);

只要传进来的对象实现了 Payment 接口,就可以完成支付。

以后新增银行卡支付:

java 复制代码
class BankCardPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用银行卡支付:" + amount + " 元");
    }

    @Override
    public void refund(double amount) {
        System.out.println("银行卡退款:" + amount + " 元");
    }
}

原来的 OrderService 不需要修改。

java 复制代码
Payment bankCardPay = new BankCardPay();
orderService.checkout(bankCardPay, 300);

这符合一个重要设计原则:

对扩展开放,对修改关闭。

也就是说,新增功能时,尽量新增代码,而不是修改老代码。


13. 真实开发场景:模板方法适合用抽象类

假设我们要设计一个"做饭流程"。

不同菜的做法不同,但整体流程差不多:

  1. 准备食材;
  2. 开火;
  3. 烹饪;
  4. 装盘。

其中"开火"和"装盘"可能是公共步骤,而"准备食材"和"烹饪"因菜而异。

这种情况适合用抽象类。


13.1 定义抽象类

java 复制代码
abstract class CookTemplate {

    public final void cook() {
        prepareFood();
        fire();
        doCook();
        dishUp();
    }

    public abstract void prepareFood();

    public void fire() {
        System.out.println("开火");
    }

    public abstract void doCook();

    public void dishUp() {
        System.out.println("装盘");
    }
}

这里的 cook() 方法定义了固定流程。

java 复制代码
public final void cook() {
    prepareFood();
    fire();
    doCook();
    dishUp();
}

final 表示不希望子类修改整体流程。


13.2 具体菜品继承抽象类

java 复制代码
class TomatoEgg extends CookTemplate {
    @Override
    public void prepareFood() {
        System.out.println("准备番茄和鸡蛋");
    }

    @Override
    public void doCook() {
        System.out.println("炒番茄鸡蛋");
    }
}
java 复制代码
class BeefNoodle extends CookTemplate {
    @Override
    public void prepareFood() {
        System.out.println("准备牛肉和面条");
    }

    @Override
    public void doCook() {
        System.out.println("煮牛肉面");
    }
}

13.3 测试代码

java 复制代码
public class Test {
    public static void main(String[] args) {
        CookTemplate tomatoEgg = new TomatoEgg();
        tomatoEgg.cook();

        System.out.println("------");

        CookTemplate beefNoodle = new BeefNoodle();
        beefNoodle.cook();
    }
}

输出结果:

text 复制代码
准备番茄和鸡蛋
开火
炒番茄鸡蛋
装盘
------
准备牛肉和面条
开火
煮牛肉面
装盘

14. 为什么这个做饭案例适合用抽象类?

因为这里有明显的父子关系和公共流程:

text 复制代码
番茄炒蛋 是一种 做饭流程
牛肉面 是一种 做饭流程

抽象类可以做到:

  • 固定整体流程;
  • 复用公共方法;
  • 让子类实现差异步骤。

接口更适合定义能力,而抽象类更适合定义模板和公共骨架。


15. 抽象类和接口的核心区别

对比点 抽象类 接口
核心思想 描述"是什么" 描述"能做什么"
关系 is-a 关系 has-a-capability 能力关系
关键字 abstract class interface
使用方式 extends implements
继承数量 一个类只能继承一个抽象类 一个类可以实现多个接口
成员变量 可以有普通变量 默认是 public static final 常量
构造方法 可以有构造方法 不能有构造方法
普通方法 可以有普通方法 Java 8 后可以有 default 方法和 static 方法
主要作用 代码复用、模板设计、父子关系 规范约束、能力扩展、解耦合

16. 如何判断该用抽象类还是接口?

可以问自己两个问题。

16.1 这些类是不是同一种东西?

如果是,优先考虑抽象类。

例如:

text 复制代码
Dog 是 Animal
Cat 是 Animal
Student 是 Person
Teacher 是 Person

适合:

java 复制代码
abstract class Animal {}
abstract class Person {}

16.2 这些类是不是只是有相同能力?

如果是,优先考虑接口。

例如:

text 复制代码
Bird 可以 fly
Airplane 可以 fly
Superman 可以 fly

适合:

java 复制代码
interface Flyable {}

17. 一个完整的综合例子

我们设计一个校园系统。

学校里有老师和学生:

  • 老师是人;
  • 学生也是人;
  • 老师可以教学;
  • 学生可以学习;
  • 有些老师会开车;
  • 有些学生也会开车。

这时可以这样设计:


17.1 抽象类 Person

java 复制代码
abstract class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sleep() {
        System.out.println(name + " 正在睡觉");
    }

    public abstract void duty();
}

17.2 接口 Drivable

java 复制代码
interface Drivable {
    void drive();
}

17.3 Teacher 类

java 复制代码
class Teacher extends Person implements Drivable {

    public Teacher(String name, int age) {
        super(name, age);
    }

    @Override
    public void duty() {
        System.out.println(name + " 的职责是教学");
    }

    @Override
    public void drive() {
        System.out.println(name + " 开车去学校");
    }
}

17.4 Student 类

java 复制代码
class Student extends Person {

    public Student(String name, int age) {
        super(name, age);
    }

    @Override
    public void duty() {
        System.out.println(name + " 的职责是学习");
    }
}

17.5 测试代码

java 复制代码
public class Test {
    public static void main(String[] args) {
        Person teacher = new Teacher("王老师", 40);
        Person student = new Student("小明", 18);

        teacher.sleep();
        teacher.duty();

        student.sleep();
        student.duty();

        Drivable driver = new Teacher("李老师", 38);
        driver.drive();
    }
}

输出结果:

text 复制代码
王老师 正在睡觉
王老师 的职责是教学
小明 正在睡觉
小明 的职责是学习
李老师 开车去学校

18. 最终总结

抽象类的本质:

抽象类是为了抽取同一类事物的共同属性和行为。

接口的本质:

接口是为了抽取不同事物的共同能力和规范。

最简单的判断方式:

text 复制代码
如果你想表达:A 是一种 B
用抽象类

如果你想表达:A 具备某种能力
用接口

例如:

java 复制代码
class Dog extends Animal

表示:

text 复制代码
狗是一种动物
java 复制代码
class Bird implements Flyable

表示:

text 复制代码
鸟具备飞行能力

19. 记忆口诀

抽象类看身份,接口看能力。

抽象类重复用,接口重扩展。

抽象类是父子关系,接口是能力规范。


20. 面试回答模板

如果面试官问:"抽象类和接口有什么区别?"

可以这样回答:

抽象类和接口都可以实现抽象设计,但它们解决的场景不同。抽象类主要用于描述同一类事物的共同特征,强调的是"是什么",适合有父子关系、需要代码复用的场景。比如 Dog extends Animal,表示狗是一种动物。接口主要用于描述某种能力或规范,强调的是"能做什么",适合不同类型对象具有相同能力的场景。比如 Bird implements Flyable,表示鸟具备飞行能力。Java 中一个类只能继承一个抽象类,但可以实现多个接口,所以接口更适合能力扩展和系统解耦。

相关推荐
未若君雅裁1 小时前
死锁产生条件与诊断:jps、jstack、VisualVM
java·开发语言
大蚂蚁2号1 小时前
Python迭代器与生成器深度剖析:从底层协议到工程实战
python
用户925807911481 小时前
画图理解mysql日志机制
java·后端
专注搞钱1 小时前
AI编程实战:我用Python+LangChain搭建了一个半导体FAB智能运维Agent
python·langchain·ai编程
梦里捡到一只猫丶1 小时前
简单的Payload加密方法
笔记·网络安全
javahongxi1 小时前
Spring Cloud Trace 链路实现
java·spring boot·spring cloud
海梨花1 小时前
腾讯面试高频算法题
java·算法·面试
于先生吖1 小时前
Java消息队列优化抢单逻辑,同城搬家拉货多场景业务数据库架构设计
java·开发语言·数据库架构
半个烧饼不加肉1 小时前
JS 底层探究--执行上下文
开发语言·前端·javascript