空对象模式是用可交互对象替代null,适用于有明确行为契约的接口/抽象类场景,如NullDeliveryPolicy;不适用于简单字段或语义模糊的null,需避免泛滥且确保行为可预期、易识别、无副作用。

空对象模式不是为了“消灭 null”,而是用一个真正可交互的对象替代 null,让调用方无需关心“有没有”,只管“怎么用”。它真正起效的地方,是当多个业务分支反复出现 if (obj != null) { ... } 或 Optional.ofNullable(...).map(...) 时——这时引入空对象,代码会立刻变干净。
什么时候该用空对象,而不是 Optional 或判空?
空对象适合有明确行为契约的接口或抽象类场景。比如订单服务中有个 DeliveryPolicy 接口,不同地区实现不同配送规则;但某些新上线城市还没配置策略,此时返回 null 会让所有调用方加判空,而返回一个 NullDeliveryPolicy(默认不加急、不补贴、不校验库存),就能让主流程完全无感。
- ✅ 适用:接口/抽象类已存在,且“无行为”本身有明确语义(如“不执行”“跳过”“返回空集合”)
- ❌ 不适用:简单字段(如
String name)、DTO 中的原始类型包装类、或“空”含义模糊(比如用户对象为 null,是未登录?还是查无此人?语义不统一) - ⚠️ 注意:不要为每个可能为 null 的变量都建一个空对象——那会泛滥。优先覆盖高频、跨多处使用的协作角色
如何设计一个真正好用的空对象?
关键不是“让它不报错”,而是“让它行为可预期、易识别、难误用”。一个合格的空对象应满足:
-
实现同一接口,且方法全部非空返回:避免在空对象里又抛 NPE 或返回 null,例如
getItems()返回Collections.emptyList(),而非 null -
显式命名,拒绝隐晦:用
NullPaymentGateway,别叫DefaultPaymentGateway——后者容易让人误以为是真实默认实现 -
重写
toString()和isPresent()(如有)等辅助方法:便于日志排查和调试,例如返回"NullPaymentGateway[disabled-by-config]" - 禁止在空对象里做副作用操作:比如发消息、改状态、打日志(除非明确用于追踪空路径)
与 Spring、Lombok 等框架协同的实用技巧
在真实项目中,空对象常配合依赖注入使用:
- Spring 中可用
@Primary+ 条件化 Bean(@ConditionalOnMissingBean)自动兜底:当业务未注册具体策略 Bean 时,自动装配空对象实例 - Lombok 的
@UtilityClass可封装空对象的静态获取入口,如NullDeliveryPolicy.instance(),比每次new更清晰 - 若已有大量判空逻辑,可先提取共用方法(如
safeGetPolicy(user)),内部统一返回空对象——逐步迁移,不强求一步重构
一个微小但关键的提醒
空对象不是银弹。它解决的是“调用链上重复防御性检查”的问题,不是“数据缺失”的问题。如果某处频繁拿到 null,更该问的是:为什么这个对象本该存在却没构造出来?是上游漏了初始化?配置没加载?还是领域模型本身没表达清楚“可选性”? 空对象只是优雅地掩盖症状,真正的根因仍需追溯。

















