【Java学习】【Java基础】--第2篇:抽象类和接口

一、抽象类和接口的区别

特点 抽象类 接口
关系 “是一个”的关系(圆形是几何形状的一种) “能做什么”的关系(微波炉能够加热)
方法 可以有抽象方法和具体方法 主要是抽象方法,可以有默认方法(default)
变量 可以有普通成员变量 变量默认是public static final(常量)
继承 单继承 多实现
构造器
设计理念 代码复用,模板方法模式 定义行为,多态

1.抽象类

abstract class Shape {
    private String name;

    public Shape(String name) {
        this.name = name;
    }

    // 抽象方法,每种形状计算面积的方式不同
    public abstract double calculateArea();

    // 具体方法,所有形状共享行为
    public void displayInfo() {
        System.out.println("这是一个" + name);
        System.out.println("面积是: " + calculateArea());
    }
}

class Circle extends Shape {

    private double radius;

    public Circle(double radius) {
        super("圆形");
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {

    private double length;
    private double width;

    public Rectangle(double length, double width) {
        super("矩形");
        this.length = length;
        this.width = width;
    }

    @Override
    public double calculateArea() {
        return length * width;
    }
}

public class Main {

    public static void main(String[] args) {

        Circle circle = new Circle(3);
        Rectangle rectangle = new Rectangle(1, 2);

        // 多态
        Shape[] shapes = {circle, rectangle};
        for (Shape s: shapes) {
            s.displayInfo();
        }

        // 这是一个圆形
        // 面积是: 28.274333882308138
        // 这是一个矩形
        // 面积是: 2.0

    }
}

2.接口

// 定义加热功能
interface Heatable {
    void heat(); // 默认就是public abstract
}

// 定义定时功能
interface Timable {
    void setTimer(int minuters);

    // 默认方法
    default void displayTime() {
        System.out.println("当前时间已显示");
    }
}

class Microwave implements Heatable, Timable {
    @Override
    public void heat() {
        System.out.println("微波炉正在加热");
    }

    @Override
    public void setTimer(int minuters) {
        System.out.println("微波炉设置定时" + minuters + "分钟");
        displayTime();
    }
}

class Kettle implements Heatable {
    @Override
    public void heat() {
        System.out.println("水壶正在加热");
    }
}

public class Main {
    public static void main(String[] args) {
        Microwave microwave = new Microwave();
        Kettle kettle = new Kettle();

        Heatable[] heatables = {microwave, kettle};
        for (Heatable h: heatables) {
            h.heat();
        }

        // 微波炉正在加热
        // 水壶正在加热

        // Timable[] timables = {microwave, kettle}; 编译报错,kettle没有实现Timable,其对象无法指向Timable的引用
    }
}

二、访问权限

类型 方法种类 允许的访问修饰符 说明
抽象类 抽象方法 public,protected,默认 不能是private,因为抽象方法要重写
抽象类 普通方法 所有权限 包括private
接口 抽象方法 只能是public 默认就是public abstract
接口 默认方法 只能是public Java 8+
接口 静态方法 只能是public Java 8+
接口 私有方法 只能是private Java 9+

三、总结

  • 抽象类:半成品要继承,填空白才能用。像普通类一样自由,但是抽象方法不能使用private
  • 接口:说明书定规矩,多功能随便用。以前只能公开public,Java9以后可以有私有方法private

四、AI提出的问题

借助AI工具,提出了几个与抽象类、接口有关的问题,记录一下我的回答和AI提供的答案

1. 从语法层面,列出抽象类和接口的三个主要区别

我的回答:抽象类有构造器,接口没有。抽象类只能单继承,接口可以多实现。抽象类可以有具体方法,接口在java8之前只能有抽象方法。抽象类是is-a的关系,接口是has-a的关系

AI的回答:

  1. 构造器方面:抽象类可以有构造器;接口不能有构造器

  2. 方法实现方面:抽象类可以有抽象方法和具体方法;接口在Java8之前只能有抽象方法,Java8之后支持默认方法和静态方法,Java9之后支持私有方法

  3. 继承方面:抽象类只能单继承;接口可以多实现

  4. 成员变量方面:抽象类可以使用各种访问修饰符,包括private;接口默认public static final,Java9之后才支持私有变量

  5. 设计理念:抽象类是is-a的关系;接口是has-a/can-do的关系

2. 以下代码能否编译通过?为什么?

public abstract class Device {
    public abstract void start();
    
    public void stop() {
        System.out.println("Device stopping...");
    }
}

public interface Controllable {
    void start();
    void stop();
}

public class Robot extends Device implements Controllable {
    public void start() {
        System.out.println("Robot starting...");
    }
    // 缺少stop()方法实现?
}

我的回答:不能,抽象类Device和接口Controllable都有方法start需要重写,二者冲突

AI的回答:这个代码实际上可以编译通过。原因如下:

Robot类从Device抽象类继承了具体的stop()方法实现,这个具体实现满足了Controllable接口对stop()方法的要求。在Java中,父类的具体方法可以满足接口的方法要求。

只有当出现以下情况时才会编译错误:

  • 多个接口有相同的默认方法,且实现类没有显式重写
  • 父类方法与接口方法签名冲突

3. 什么时候应该使用抽象类?什么时候应该使用接口?请结合你的工业控制背景举例说明

我的回答:当几个类都具有共同方法,但是这些方法又因为个体不同而不同时,采用抽象类,例如机器人和焊机都是设备,都有run()方法,但是具体实现又不一样,就可以抽象出Device这个抽象类。而当几个类需要有不同的功能时,就可以使用接口,例如机器人有远程功能,就可以定义Remote接口,焊机有焊接功能,就可以定义Weld接口

AI的回答:

使用抽象类的场景

  • 当多个类有共同的属性和行为,但某些行为需要子类具体实现时
  • 需要定义算法的固定骨架,但允许子类改变某些步骤时(模板方法模式)
  • 需要共享状态和代码复用时

使用接口的场景

  • 定义不同类可以具备的能力或行为
  • 需要实现多继承的效果时
  • 定义API契约,不关心具体实现时
  • 在框架中定义回调机制时

你的工业控制例子很贴切

  • 抽象类Device定义所有设备的共同属性和基本行为

  • 接口RemoteControllable、DataCollectable定义设备可以具备的特定能力

4. 假设你要设计一个工业设备控制系统,有以下需求:

  • 所有设备都有启动、停止、获取状态的基本操作
  • 某些设备支持远程控制,需要安全认证
  • 某些设备支持数据采集和历史记录查询
  • 未来可能增加新的设备类型和功能

请用抽象类和接口设计这个系统,说明你的设计理由

我的回答:定义一个Device类,抽象方法为start,stop和getStatus,需要子类实现,同时有个public final修饰的固定步骤方法work来调用这些方法。定义Remote,Secure,GetData,Log等接口,有需要的设备进行实现。这样的目的是当有新设备需要添加时,继承Device进行重写即可,对不同功能的接口进行重新,新功能的话也可以直接添加接口,不影响旧功能的使用。

AI的回答:

抽象类设计

  • 定义所有工业设备的共同属性(设备ID、状态、生产日期等)
  • 提供构造器进行初始化
  • 定义抽象方法(initialize、shutdown等)让子类实现
  • 使用final修饰模板方法,定义固定流程

接口设计

  • RemoteControllable:远程控制能力
  • DataCollectable:数据采集能力
  • MaintenanceTrackable:维护跟踪能力
  • SafetyCertifiable:安全认证能力

设计优势

  • 新设备只需继承抽象类并实现相关接口
  • 新增功能只需添加新接口,不影响现有代码
  • 符合开闭原则,对扩展开放,对修改关闭

5. 模板方法模式通常使用抽象类实现,为什么?请用代码示例说明

我的回答:模板方法模式不太了解

AI的回答:

模板方法模式的核心是定义算法的骨架,将某些步骤延迟到子类实现。抽象类适合这种模式是因为:

  1. 固定流程:抽象类可以定义final的模板方法,确保算法流程不变
  2. 步骤抽象:将变化的部分定义为抽象方法,由子类实现
  3. 代码复用:不变的部分在抽象类中实现,避免代码重复

工业控制中的例子
设备启动流程:电源检查 → 初始化 → 自检 → 设置运行状态
这个固定流程定义在抽象类中,具体设备的初始化和自检由子类实现。

6. 策略模式通常使用接口实现,为什么?请用代码示例说明

我的回答:策略模式不太了解

AI的回答:

策略模式的核心是定义一系列算法,使它们可以互相替换。接口适合这种模式是因为:

  1. 算法抽象:接口定义算法的契约,不关心具体实现
  2. 多态支持:同一接口可以有多个实现,运行时替换
  3. 开闭原则:新增算法策略只需添加新实现,无需修改现有代码

支付系统的例子
支付接口定义pay方法,支付宝、微信支付、银联支付分别实现这个接口。支付上下文可以根据用户选择使用不同的支付策略。

7. 自从Java 8引入接口的默认方法和静态方法后,抽象类还有存在的必要吗?为什么?

我的回答:有必要,但想不出理由

AI的回答:

即使Java 8引入了接口的默认方法,抽象类仍然必要,因为:

  1. 状态管理:抽象类可以有实例变量来维护状态,接口只能有常量
  2. 构造器逻辑:抽象类可以有构造器进行复杂的初始化,接口不能
  3. 访问控制:抽象类可以有protected、private方法,接口方法默认都是public
  4. 代码复用:抽象类可以提供具体方法供子类直接复用
  5. 模板方法:需要定义固定算法流程时,抽象类是更好的选择

8. 以下代码存在什么问题?如何解决?

public interface DataProcessor {
    default void process(List<String> data) {
        System.out.println("Processing data...");
        validate(data);
    }
    
    private void validate(List<String> data) {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Data cannot be empty");
        }
    }
}

public interface AdvancedProcessor {
    default void process(List<String> data) {
        System.out.println("Advanced processing...");
        // 不同的实现
    }
}

public class IndustrialProcessor implements DataProcessor, AdvancedProcessor {
    // 这里会出现什么问题?
}

我的回答:两个接口有同名且同参数列表的默认方法,实现两个接口的类不知道该调用哪个,编译会报错。

AI的回答:

问题原因
当类实现多个接口,且这些接口有同名同参数的默认方法时,编译器无法确定使用哪个实现。

解决方案

  1. 显式重写:在实现类中重写冲突方法,明确选择其中一个实现:
    • InterfaceA.super.method()
    • InterfaceB.super.method()
  2. 提供新实现:完全重写方法,提供新的实现逻辑
  3. 接口设计优化
    • 让一个接口继承另一个接口,避免冲突
    • 使用抽象类作为适配器
  4. 默认方法规范:在设计接口时,避免定义容易冲突的默认方法

9. 在Spring框架中,为什么我们通常用接口来定义Service层?这样设计有什么好处?

我的回答:还没学到Spring

AI的回答:

Spring中大量使用接口的主要原因

  1. 解耦:Controller依赖Service接口,不依赖具体实现,降低耦合度
  2. 便于测试:可以创建Mock实现进行单元测试,不依赖真实服务
  3. 支持AOP:Spring基于接口实现动态代理,可以轻松添加事务、日志等横切关注点
  4. 多实现支持:一个接口可以有多个实现,通过@Qualifier或@Primary选择
  5. 符合设计原则
    • 依赖倒置原则:依赖抽象,不依赖具体
    • 开闭原则:新增实现不影响现有代码

10. 如果你要设计一个支付系统,支持支付宝、微信支付、银联支付,你会如何使用抽象类和接口来保证系统的扩展性和维护性?

我的回答:对我来说有点困难

AI的回答:

完整的设计思路

1. 接口层设计

  • PaymentService接口:定义支付、退款等核心方法
  • 使用PaymentRequest、PaymentResult等DTO封装数据

2. 抽象类设计

  • AbstractPaymentService:处理通用逻辑
    • 请求验证
    • 日志记录
    • 异常处理
    • 性能监控

3. 具体实现

  • AlipayService:支付宝支付实现
  • WechatPayService:微信支付实现
  • UnionPayService:银联支付实现

4. 工厂模式

  • PaymentServiceFactory:管理所有支付服务
  • 根据支付类型返回对应的支付服务

5. 扩展性考虑

  • 新增支付方式只需实现PaymentService接口
  • 支付类型使用枚举管理
  • 支持运行时切换支付策略

设计优势

  • 符合开闭原则,易于扩展
  • 代码复用,避免重复逻辑
  • 支持多种支付策略灵活切换
  • 便于单元测试和Mock

有不少关于设计的问题还没法完全理解,后续回头来看

posted @ 2025-11-25 00:38  JustForFun1  阅读(9)  评论(0)    收藏  举报