news 2026/4/23 15:42:05

《JAVA面经实录》- 设计模式面试题(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《JAVA面经实录》- 设计模式面试题(一)

《JAVA面经实录》- 设计模式面试题(一)

这份是设计模式面试题・标准答案背诵版语言精炼、口语化、不啰嗦,面试官最爱听,直接背就能过。

一、基础必问题(标准答案)

1.设计模式三大类?

  • 创建型:控制对象创建(单例、工厂、建造者、原型、抽象工厂)。
  • 结构型:组合类与对象结构(代理、装饰、适配器、外观、桥接、组合、享元)。
  • 行为型:对象间通信与职责(策略、模板、观察者、责任链、状态等)。

2.六大设计原则?

设计模式六大原则是设计的核心准则,核心目标是“高内聚、低耦合”,简单解释如下(面试口述无需过于复杂):

1. 开闭原则:对扩展开放,对修改关闭(新增功能靠扩展,不修改原有代码);

2. 单一职责原则:一个类/接口只负责一项职责(避免职责混乱,便于维护);

3. 里氏替换原则:子类可替换父类,且不改变原有程序的正确性(保证继承的合理性);

4. 依赖倒置原则:依赖接口/抽象,不依赖具体实现(降低耦合,提升扩展性);

5. 接口隔离原则:接口拆分细化,不强迫类实现不需要的方法(避免接口臃肿);

6. 合成复用原则:优先使用组合/聚合,而非继承(降低耦合,提升灵活性)。

3.什么是开闭原则?为什么它最重要?

开闭原则核心定义:软件实体(类、接口、方法)对扩展开放,对修改关闭。也就是说,新增功能时,通过新增类、接口或方法实现,不修改原有核心代码;

最重要的原因:

  • 不修改原有代码,避免引入 bug,保证系统稳定
  • 易于扩展,符合架构长期演进

4.合成复用原则是什么?为什么优先使用组合而不是继承?

合成复用原则:尽量使用组合(has-a)或聚合(contains-a)的方式复用代码,而非继承(is-a)的方式;

优先组合而非继承的核心原因:

  • 继承强耦合、子类泛滥、破坏封装
  • 组合灵活、低耦合、可插拔、可动态替换

5.你项目中用过哪些设计模式?解决了什么问题?(满分回答)

  • 单例:全局配置、线程池、工具类
  • 工厂 + 策略:支付渠道、消除 if-else
  • 代理 / AOP:日志、权限、事务
  • 模板方法:统一业务流程
  • 观察者:事件通知、异步解耦
  • 责任链:拦截器、鉴权、风控
  • 建造者:构建复杂 DTO

6.设计模式的优缺点?过度设计有什么问题??

  • 优点:可复用、易维护、易扩展、结构清晰
  • 缺点:类数量增多,过度设计会复杂、可读性下降

过度使用设计模式的问题(核心3点):

(1). 增加系统复杂度:简单场景(比如一个简单工具类)强行使用多种模式(单例+工厂),导致代码难以理解、排查问题困难;

(2). 降低系统性能:过多的模式封装(比如多层代理、装饰器),会增加方法调用开销,影响系统响应速度;

(3). 提升维护成本:大量的模式相关类(比如策略类、工厂类),会增加后期修改、迭代的成本,违背“简单够用”的设计原则。


二、单例模式(标准答案)

1.手写 DCL 单例,为什么加 volatile?

  • 禁止指令重排
  • new Singleton()分三步:分配内存→初始化→赋值引用
  • 重排可能导致其他线程拿到半初始化对象,引发异常

加volatile的核心原因:禁止JVM指令重排。
instance = new Singleton()会被JVM拆分为3步:

① 分配内存空间;

② 初始化对象;

③ 将instance指向分配的内存地址。不加volatile时,JVM可能重排②和③的顺序,导致“半初始化对象”被其他线程获取(即instance不为null,但对象未初始化完成),进而引发空指针异常;volatile可禁止这种重排,保证3步按顺序执行,确保多线程下的安全性。
解析:代码必须完整(含防反射、防序列化),volatile的解释重点突出“指令重排”和“半初始化对象”,这是面试官考察的核心(避免只说“保证可见性”,忽略核心作用)。

2.手写 静态内部类单例,为什么线程安全??

  • 类加载机制保证静态变量只初始化一次
  • JVM 保证类初始化的线程安全,天然无锁

线程安全的核心原因:依赖JVM的类加载机制。
JVM规定,静态内部类的加载是“懒加载”(只有调用getInstance(),触发InnerClass类加载时,才会创建instance实例);且类加载过程是线程安全的(JVM在类加载时会加锁,保证同一时刻只有一个线程执行类加载逻辑),因此静态内部类单例无需额外加锁,天然保证线程安全,同时实现了懒加载,兼顾性能和安全性。
解析:重点解释“类加载机制”和“懒加载”,体现对JVM底层的理解,面试官核心考察你是否知道线程安全的底层原理,而非单纯背诵代码

3.手写 枚举单例,为什么它最安全?

  • JVM 保证枚举构造器只调用一次
  • 天然防反射、防序列化、防多类加载器破坏
  • 代码极简,是《Effective Java》推荐方式

最安全的核心原因(3点,天然防御所有破坏方式):
① 防反射:JVM底层禁止通过反射创建枚举实例(反射获取枚举构造器时会抛出异常),从根源上防止反射破坏;
② 防序列化:JVM保证枚举反序列化时,不会创建新实例,只会返回原有的枚举常量,避免序列化破坏;
③ 防多线程安全问题:枚举常量的初始化是在类加载时完成的,类加载过程线程安全,且枚举常量是天然的单例,无需额外处理。
补充:枚举单例写法简洁、无需手动处理安全问题,是面试中推荐的“最安全单例实现方式”。
解析:重点突出“天然防御反射、序列化”,这是枚举单例与其他单例的核心区别,面试官考察你对单例安全的全面理解。

4.哪些方式可以破坏单例?如何防御?

  • 反射:调用私有构造
  • 序列化:readObject 新建对象
  • 多类加载器:不同加载器加载多次

核心有3种破坏方式,对应防御方案(面试重点说前2种,第3种简要说明):

(1). 反射破坏:

破坏方式:通过Class.getDeclaredConstructor()获取私有构造器,调用setAccessible(true),强制创建新实例;

防御方案:在私有构造器中添加判断,若instance已存在,抛出异常(如DCL单例、静态内部类单例的构造器逻辑);枚举单例天然防反射。

(2). 序列化破坏:

破坏方式:将单例实例序列化后写入文件,再反序列化读取时,会创建新的实例(readObject()方法默认新建对象);

防御方案:在单例类中重写readResolve()方法,返回单例实例(return getInstance());枚举单例天然防序列化。

(3). 多类加载器破坏:

破坏方式:不同的类加载器(如自定义类加载器、系统类加载器)分别加载单例类,会产生多个实例;

防御方案:指定统一的类加载器加载单例类,或在getInstance()方法中判断类加载器是否一致。

解析:面试官核心考察你对单例“安全隐患”的认知,每个破坏方式对应具体防御方案,避免只说“破坏方式”不说“如何防御”。

5.序列化为什么会破坏单例?怎么解决?

  • 防反射:构造器判断已存在则抛异常
  • 防序列化:提供 readResolve 返回单例
  • 多类加载:统一使用同一个类加载器

(1). 破坏原因:Java序列化的核心是“将对象转为字节流,反序列化时重新创建对象”,默认情况下,反序列化会调用readObject()方法,该方法会忽略单例的限制,重新创建一个新的实例,导致单例被破坏(即原实例和反序列化后的实例不是同一个)。

(2). 解决方法(2种,优先第一种):

① 重写readResolve()方法:在单例类中添加private Object readResolve()方法,返回单例实例(return getInstance()),反序列化时会优先调用该方法,返回已存在的单例,而非创建新实例;

② 使用枚举单例:枚举单例天然防御序列化破坏,JVM底层在反序列化时,会直接返回原有的枚举常量,不会创建新实例。

解析:重点解释“反序列化会重新创建对象”这一核心原因,解决方案要具体,结合代码(readResolve()方法),体现实战性。

6.Spring Bean 默认是单例,它是如何实现的??

  • 三级缓存 + 注册式单例
  • 把 Bean 放入单例池singletonObjects,全局复用

Spring Bean默认单例,核心通过“单例注册表模式”实现,底层依赖BeanFactory(DefaultListableBeanFactory),具体流程如下:

(1). 注册表存储:Spring内部维护一个Map(单例注册表,key是Bean的id/name,value是Bean实例),用于缓存已创建的单例Bean;

(2). 懒加载创建:默认情况下,Spring在容器启动时不会创建单例Bean,而是在第一次获取Bean(如getBean())时,才会创建Bean实例,创建后存入注册表;

(3). 唯一实例保证:每次获取Bean时,先检查注册表中是否已有该Bean实例,若有则直接返回,若无则创建实例并存入注册表,确保全局只有一个实例;

(4). 扩展补充:Spring单例是“容器级单例”(同一个Spring容器中,Bean是单例;不同容器中,Bean是不同实例),而非“JVM级单例”。

解析:重点突出“单例注册表”和“缓存机制”,结合Spring底层实现,体现你对Spring的理解,避免只说“Spring内部维护一个Map”,要说明完整流程。


三、创建型模式(标准答案)

1.简单工厂 / 工厂方法 / 抽象工厂的区别?

  • 简单工厂:一个工厂造所有,违反开闭
  • 工厂方法:一个产品一个工厂,符合开闭
  • 抽象工厂:造产品族(手机 + 电脑),强约束系列产品

三者核心区别在于“工厂的职责范围”和“扩展性”,核心对比如下(面试口述重点说核心差异):

(1). 简单工厂(静态工厂):

核心:一个工厂类负责所有产品的创建,通过if-else判断产品类型,返回对应实例;

特点:结构简单、开发快速;但违背开闭原则,新增产品需修改工厂类的判断逻辑,耦合度高。

(2). 工厂方法:

核心:一个产品对应一个工厂,定义抽象工厂接口,每个具体产品实现对应的具体工厂;

特点:符合开闭原则,新增产品只需新增产品类和对应工厂类,无需修改原有代码;但工厂类数量会随产品增加而增多,增加维护成本。

(3). 抽象工厂:

核心:一个工厂负责一个“产品族”(一组相关联的产品),定义抽象工厂接口,具体工厂实现该接口,生产一组产品;

特点:适合平台化、标准化场景,能保证产品族的一致性;但新增“产品等级”(如产品族中新增一个新产品)需修改抽象工厂接口,违背开闭原则。

总结:简单工厂适合简单场景,工厂方法适合单一产品线扩展,抽象工厂适合多产品族场景。

解析:面试官核心考察你对三种工厂模式“扩展性”和“职责边界”的理解,避免只说结构不同,重点突出“开闭原则的适配性”和“适用场景”。

2.抽象工厂的产品族和产品等级是什么?

  • 产品族:同一个品牌 / 系列的多个产品
  • 产品等级:同一个产品的不同品牌实现

产品族和产品等级是抽象工厂模式的核心概念,用简单例子(电子设备)说明,易懂好记:

(1). 产品族:同一品牌、同一风格的一组相关联产品,由一个工厂统一生产,核心是“关联关系”;

举例:华为产品族(华为手机、华为电脑、华为耳机),苹果产品族(苹果手机、苹果电脑、苹果耳机),每组产品都是同一品牌的关联产品。

(2). 产品等级:同一类型、不同品牌的产品,核心是“同类型”;

举例:手机产品等级(华为手机、苹果手机、小米手机),电脑产品等级(华为电脑、苹果电脑、小米电脑),每组产品都是同一类型、不同品牌。

补充:抽象工厂的核心作用,就是生产“一个产品族”的所有产品,保证产品族内的产品适配性,而不负责生产不同产品等级的产品。

解析:面试官考察你对抽象工厂核心概念的理解,用具体例子辅助,避免抽象,重点区分“关联关系(产品族)”和“同类型(产品等级)”。

3.建造者模式和工厂模式的区别

  • 工厂:关注产品是什么
  • 建造者:关注如何一步步组装,适合多参数复杂对象

两者都属于创建型模式,核心区别在于“创建目标”和“创建逻辑”,重点区分“整体创建”和“分步构建”:

(1). 核心目标:

① 工厂模式:核心是“快速创建单一产品实例”,不关心产品的构建过程,只关心“创建什么”;

② 建造者模式:核心是“分步构建复杂产品实例”,关心产品的构建细节,控制产品的各个组成部分,最终组装成完整产品。

(2). 适用场景:

① 工厂模式:产品结构简单,创建逻辑单一(如支付方式、简单工具类);

② 建造者模式:产品结构复杂,由多个部分组成,需要分步构建(如订单对象、用户信息对象、汽车对象)。

举例:工厂模式直接创建“手机”实例,建造者模式分步构建“手机”(先构建屏幕、再构建电池、最后组装)。

解析:重点突出“是否关心构建过程”,这是两者最核心的区别,面试官考察你是否能根据产品复杂度,选择合适的创建模式。

4.原型模式的浅克隆和深克隆区别?

  • 浅克隆:复制基本类型,引用对象共享
  • 深克隆:完全复制,引用对象独立,常用序列化实现

核心区别:是否复制“引用类型属性”,是否保证原对象和克隆对象的完全独立,具体如下:

(1). 浅克隆(浅拷贝):

① 复制规则:只复制对象的基本类型属性(int、String等),对于引用类型属性(Map、List、自定义对象),只复制引用地址,不复制引用指向的对象;

② 特点:原对象和克隆对象的引用类型属性共享同一个实例,修改原对象的引用属性,克隆对象的对应属性也会变化;实现简单(重写clone()方法,实现Cloneable接口)。

(2). 深克隆(深拷贝):

① 复制规则:完全复制对象的所有属性,包括基本类型和引用类型属性,引用类型属性会被重新创建,形成一个独立的实例;

② 特点:原对象和克隆对象完全独立,互不影响,修改任何一方的属性,都不会影响另一方;实现相对复杂。

举例:对象A包含一个List属性,浅克隆后,A和克隆对象的List指向同一个集合;深克隆后,两者的List是两个独立的集合。

解析:面试官核心考察你对“引用类型复制”的理解,重点区分“共享引用”和“独立实例”,避免只说“浅克隆复制表面,深克隆复制全部”这种笼统的表述。

5.如何实现一个深克隆?有哪些方式?

常用3种方式,按“实战推荐度”排序,面试重点说前2种,说明优缺点:

1. 序列化/反序列化(最常用、最推荐):

实现步骤:① 让需要克隆的对象及其所有引用类型属性,都实现Serializable接口;② 通过ObjectOutputStream将对象序列化,写入字节流;③ 通过ObjectInputStream将字节流反序列化,生成新的对象(即深克隆实例)。

优点:实现简单,无需手动处理引用类型属性的克隆,适配所有复杂对象;缺点:序列化会忽略transient修饰的属性,无法克隆该类属性。

2. 递归克隆(手动实现):

实现步骤:① 让对象实现Cloneable接口,重写clone()方法;② 在clone()方法中,不仅克隆自身的基本类型属性,还对引用类型属性递归调用clone()方法,将克隆后的引用属性赋值给新对象。

优点:灵活,可自定义克隆规则,能克隆transient修饰的属性;缺点:实现复杂,引用类型属性较多时,代码冗余,维护成本高。

3. JSON工具克隆(简单但有局限):

实现步骤:用FastJSON、Gson等工具,将对象转为JSON字符串,再将JSON字符串转回对象,生成的新对象即为深克隆实例。

优点:代码极简,无需实现任何接口;缺点:无法克隆transient属性、静态属性,且性能略差于序列化方式。

解析:面试官考察你对深克隆实战实现的掌握,重点说明“常用方式”和“优缺点”,体现实战经验,避免只说理论不结合实际。

6.为什么要使用原型模式?适用什么场景?

(一)、使用原型模式的核心原因(解决的问题):

(1). 提升性能:避免频繁创建复杂对象(如对象创建需要大量IO、数据库查询、复杂计算),通过克隆已有实例,减少对象创建的开销;

(2). 简化创建:复杂对象的创建逻辑繁琐,克隆已有实例无需重复编写创建逻辑,简化开发;

(3). 保持一致性:克隆实例与原实例结构一致,可基于原实例修改部分属性,快速生成相似对象,保证对象结构的一致性。

(二)、适用场景(面试重点说3种高频场景):

(1). 复杂对象创建场景:对象创建成本高(如数据库连接池中的连接对象、大量配置的全局对象);

(2). 批量生成相似对象场景:需要生成多个结构相似、仅部分属性不同的对象(如批量生成订单对象、用户信息对象);

(3). 不确定对象类型场景:在运行时动态获取对象类型,通过克隆生成新实例(如插件扩展、动态代理场景)。

补充:原型模式常与单例模式结合使用,用于缓存复杂单例对象,提升系统性能。

解析:面试官核心考察你对原型模式“实际价值”的理解,避免只说“克隆对象”,重点说明“解决了什么问题”和“适用的实战场景”,体现架构思维。


四、结构型模式(标准答案)

1.静态代理、JDK 动态代理、CGLIB 区别?

  • 静态代理:编码实现,耦合高
  • JDK:基于接口,继承 Proxy
  • CGLIB:基于继承,ASM 生成子类,无需接口

2.JDK 代理为什么必须基于接口?

  • 生成的代理类已经继承Proxy,Java 单继承限制,只能实现接口

3.Spring AOP 用什么代理?SpringBoot 为什么默认 CGLIB?

  • 统一代理逻辑,不管有没有接口
  • 避免因代理方式不同导致行为不一致

4.装饰器 vs 代理

  • 装饰器:增强功能,对象外部传入,强调嵌套扩展
  • 代理:控制访问,隐藏目标对象,强调权限、拦截、管控

5.适配器 vs 装饰器

  • 适配器:接口转换,兼容不兼容结构
  • 装饰器:功能增强,接口不变

6.外观 vs 中介者

  • 外观:单向简化调用,隐藏子系统
  • 中介者:双向调度,把网状依赖变星型

7.享元内部状态 vs 外部状态

  • 内部:共享不变
  • 外部:独立可变

8.桥接模式解决什么?

  • 多维度独立扩展,避免类爆炸
  • 典型:JDBC、消息类型 + 发送渠道

9.组合模式场景

  • 树形结构:文件目录、菜单、组织架构、规则树

五、行为型模式(标准答案)

1.策略模式如何消除 if-else?

  • 定义策略接口,每种实现对应一个策略
  • Map缓存策略,通过类型直接获取
  • 完全去掉分支判断,符合开闭

2.策略 vs 状态

  • 策略:外部指定算法
  • 状态:内部状态自动切换行为

3.模板方法 + 好莱坞原则

  • 父类定义流程骨架,子类实现细节
  • 好莱坞原则:Don’t call us, we’ll call you
  • 父类主导流程,子类不颠覆整体结构

4.观察者 vs 发布订阅

  • 思想一致,都是一对多通知
  • 观察者:同步、直接耦合
  • 发布订阅:异步、中间件解耦(MQ、事件总线)

5.责任链模式场景

  • 过滤器、拦截器、审批流、风控、网关插件
  • 可插拔、可编排、可动态配置

6.责任链如何中断?

  • 不调用next.handle()即可中断

7.状态模式实现订单状态

  • 每个状态封装行为
  • 状态切换由状态自身控制
  • 消除大量if(status)

8.命令模式为何支持重试 / 撤销?

  • 请求被封装为对象,可保存、排队、序列化、回放
  • 支持记录日志、回滚、重试

9.访问者双分派

  • 第一次:根据访问者类型
  • 第二次:根据元素类型
  • 从而确定执行哪个方法

10.中介者为什么会变上帝类?

  • 所有逻辑都往里写,职责过重
  • 解决:按领域拆分多个中介者

六、手写题思路(背诵版)

1.DCL 单例

  • volitale + 双层判空 + synchronized
  • 构造器私有 + 防反射

2.静态内部类单例

  • 静态内部类持有实例
  • getInstance 直接返回

3.枚举单例

  • 一行INSTANCE;搞定

4.策略模式

  • 接口 + 多个实现
  • Context 持有策略
  • Map 路由

5.JDK 动态代理

  • InvocationHandler
  • Proxy.newProxyInstance

6.责任链

  • 抽象 Handler+next
  • 子类实现 handle

七、架构场景题(标准答案)

1.重构 if-else

  • 策略模式 + 工厂 + Map 路由
  • 消除分支,易于扩展

2.可扩展支付系统

  • 策略 + 工厂方法
  • 每种支付一个实现,动态注入

3.订单状态机

  • 状态模式
  • 状态驱动行为,自动流转

4.统一日志 / 权限切面

  • 动态代理 + AOP
  • 横切逻辑与业务解耦

5.插件系统

  • 责任链 + 策略 + SPI
  • 可插拔、可配置

6.事件驱动 EDA

  • 观察者 + 消息队列
  • 异步、解耦、削峰填谷

7.审批流 / 风控

  • 责任链 + 规则引擎
  • 可编排、可配置、可监控

8.多数据源 / MQ 适配

  • 适配器 + 抽象工厂
  • 统一接口,不同实现无缝切换

9.重试 / 降级 / 熔断

  • 命令模式 + 代理
  • 请求封装,支持重试、回滚

10.低代码规则引擎

  • 解释器 + 访问者 + 组合模式
  • 语法树解析、遍历执行

八、框架源码题(标准答案)

1.Spring 用到的模式

  • 单例、工厂、代理、模板、观察者、策略、适配器、装饰器

2.MyBatis 模式

  • 代理(Mapper)、建造者、责任链(插件)、策略、模板

3.Spring AOP 本质

  • 动态代理模式

4.MyBatis Mapper 为何没有实现类

  • JDK 动态代理生成代理对象

5.IO 流

  • 装饰器模式

6.Spring Event

  • 观察者模式

7.JDBC Driver

  • 桥接模式

8.StringBuilder

  • 建造者模式

9.Integer.valueOf 缓存

  • 享元模式

九、陷阱题(标准答案)

1.装饰器可以无限嵌套吗?

  • 可以,但会加深栈深度,可能栈溢出

2.责任链过长问题

  • 性能损耗、排查困难
  • 优化:异步、合并节点、可视化编排

3.策略模式类爆炸?

  • 配合 Lambda、函数式接口、配置化减少类数量

4.final 类为何不能 CGLIB?

  • CGLIB 基于继承,final 不能被继承

5.多类加载器下单例失效?

  • 类只在对应类加载器唯一,不同加载器是不同类

6.克隆会执行构造方法吗?

  • 浅克隆不会,直接内存复制

7.策略 vs 状态怎么区分?

  • 看是否自动切换:自动 = 状态,手动指定 = 策略

8.中介者腐化怎么避免?

  • 拆分中介者、按领域职责切分
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 15:41:13

AI写专著全攻略:从构思到完稿,AI专著生成工具助你完成20万字大作!

学术专著的严谨来源于大量资料和数据的支持,而资料的搜集与数据的整合,往往是写作中最为繁琐且耗时的环节。研究者需要全面检索国内外最新的文献,不仅要关注文献的权威性和相关性,还需追溯到原始出处,避免引用错误&…

作者头像 李华
网站建设 2026/4/23 15:38:15

5分钟掌握Qwerty Learner:3步解决英语打字肌肉记忆难题

5分钟掌握Qwerty Learner:3步解决英语打字肌肉记忆难题 【免费下载链接】qwerty-learner 为键盘工作者设计的单词记忆与英语肌肉记忆锻炼软件 / Words learning and English muscle memory training software designed for keyboard workers 项目地址: https://gi…

作者头像 李华
网站建设 2026/4/23 15:38:09

LAVIS-BLIP2实战:从零构建多模态AI应用

1. 认识LAVIS和BLIP2:多模态AI的瑞士军刀 第一次接触LAVIS这个库时,我正被一个智能客服项目搞得焦头烂额。客户要求系统不仅能理解文字,还要能解读用户上传的图片。当时试了好几个方案都不理想,直到发现了LAVIS这个"多模态瑞…

作者头像 李华
网站建设 2026/4/23 15:36:57

【计算机视觉】三大分割数据集实战解析:从室内理解到自动驾驶

1. ADE20K:室内场景理解的瑞士军刀 第一次接触ADE20K数据集时,我被它细致的零部件标注震惊了。这个由MIT发布的"全能型选手"不仅能做常规的语义分割,还能精确到识别椅子的扶手、杯子的把手这种部件级细节。想象一下,当机…

作者头像 李华