【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的回答:
-
构造器方面:抽象类可以有构造器;接口不能有构造器
-
方法实现方面:抽象类可以有抽象方法和具体方法;接口在Java8之前只能有抽象方法,Java8之后支持默认方法和静态方法,Java9之后支持私有方法
-
继承方面:抽象类只能单继承;接口可以多实现
-
成员变量方面:抽象类可以使用各种访问修饰符,包括private;接口默认public static final,Java9之后才支持私有变量
-
设计理念:抽象类是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的回答:
模板方法模式的核心是定义算法的骨架,将某些步骤延迟到子类实现。抽象类适合这种模式是因为:
- 固定流程:抽象类可以定义final的模板方法,确保算法流程不变
- 步骤抽象:将变化的部分定义为抽象方法,由子类实现
- 代码复用:不变的部分在抽象类中实现,避免代码重复
工业控制中的例子:
设备启动流程:电源检查 → 初始化 → 自检 → 设置运行状态
这个固定流程定义在抽象类中,具体设备的初始化和自检由子类实现。
6. 策略模式通常使用接口实现,为什么?请用代码示例说明
我的回答:策略模式不太了解
AI的回答:
策略模式的核心是定义一系列算法,使它们可以互相替换。接口适合这种模式是因为:
- 算法抽象:接口定义算法的契约,不关心具体实现
- 多态支持:同一接口可以有多个实现,运行时替换
- 开闭原则:新增算法策略只需添加新实现,无需修改现有代码
支付系统的例子:
支付接口定义pay方法,支付宝、微信支付、银联支付分别实现这个接口。支付上下文可以根据用户选择使用不同的支付策略。
7. 自从Java 8引入接口的默认方法和静态方法后,抽象类还有存在的必要吗?为什么?
我的回答:有必要,但想不出理由
AI的回答:
即使Java 8引入了接口的默认方法,抽象类仍然必要,因为:
- 状态管理:抽象类可以有实例变量来维护状态,接口只能有常量
- 构造器逻辑:抽象类可以有构造器进行复杂的初始化,接口不能
- 访问控制:抽象类可以有protected、private方法,接口方法默认都是public
- 代码复用:抽象类可以提供具体方法供子类直接复用
- 模板方法:需要定义固定算法流程时,抽象类是更好的选择
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的回答:
问题原因:
当类实现多个接口,且这些接口有同名同参数的默认方法时,编译器无法确定使用哪个实现。
解决方案:
- 显式重写:在实现类中重写冲突方法,明确选择其中一个实现:
- InterfaceA.super.method()
- InterfaceB.super.method()
- 提供新实现:完全重写方法,提供新的实现逻辑
- 接口设计优化:
- 让一个接口继承另一个接口,避免冲突
- 使用抽象类作为适配器
- 默认方法规范:在设计接口时,避免定义容易冲突的默认方法
9. 在Spring框架中,为什么我们通常用接口来定义Service层?这样设计有什么好处?
我的回答:还没学到Spring
AI的回答:
Spring中大量使用接口的主要原因:
- 解耦:Controller依赖Service接口,不依赖具体实现,降低耦合度
- 便于测试:可以创建Mock实现进行单元测试,不依赖真实服务
- 支持AOP:Spring基于接口实现动态代理,可以轻松添加事务、日志等横切关注点
- 多实现支持:一个接口可以有多个实现,通过@Qualifier或@Primary选择
- 符合设计原则:
- 依赖倒置原则:依赖抽象,不依赖具体
- 开闭原则:新增实现不影响现有代码
10. 如果你要设计一个支付系统,支持支付宝、微信支付、银联支付,你会如何使用抽象类和接口来保证系统的扩展性和维护性?
我的回答:对我来说有点困难
AI的回答:
完整的设计思路:
1. 接口层设计:
- PaymentService接口:定义支付、退款等核心方法
- 使用PaymentRequest、PaymentResult等DTO封装数据
2. 抽象类设计:
- AbstractPaymentService:处理通用逻辑
- 请求验证
- 日志记录
- 异常处理
- 性能监控
3. 具体实现:
- AlipayService:支付宝支付实现
- WechatPayService:微信支付实现
- UnionPayService:银联支付实现
4. 工厂模式:
- PaymentServiceFactory:管理所有支付服务
- 根据支付类型返回对应的支付服务
5. 扩展性考虑:
- 新增支付方式只需实现PaymentService接口
- 支付类型使用枚举管理
- 支持运行时切换支付策略
设计优势:
- 符合开闭原则,易于扩展
- 代码复用,避免重复逻辑
- 支持多种支付策略灵活切换
- 便于单元测试和Mock
有不少关于设计的问题还没法完全理解,后续回头来看

浙公网安备 33010602011771号