java中定义ddd值对象需用final字段、无setter、不可变构造、重写equals/hashcode。字段全final且构造时赋值,集合用unmodifiablelist,禁用date改用localdatetime/instant,构造器校验参数,方法返回新对象而非修改自身。

在 Java 中定义 DDD 值对象的只读变量,核心是**用 final 修饰字段 + 无 setter + 提供不可变构造 + 正确实现 equals 和 hashCode**。这不是简单的“加个 final 就完事”,而是要确保整个对象从创建起就无法被外部改变,且语义上真正表达“值”的等价性。
用 final 字段封装所有属性
每个属性都必须声明为 final,并在构造时一次性赋值。不允许提供任何 public 或 protected 的 setter 方法,也不暴露可变集合(如直接返回 ArrayList)。
- 街道、城市、邮编都用
private final String - 如果含集合(如电话号码列表),应封装为不可变集合:
Collections.unmodifiableList(...)或使用ImmutableList(Guava) - 避免使用基本类型包装类(如
Integer)以外的可变类型,尤其警惕Date——改用LocalDateTime或Instant并同样设为final
构造函数只接受完整参数,不暴露内部状态
只提供全参构造器(或 Builder 模式),禁止无参构造 + 后续 set。构造过程即完成对象初始化,之后不再允许变更。
- 示例中
Address(String street, String city, String zipcode)是标准做法 - 若字段多或需校验,可在构造器内做非空/格式检查(如邮箱正则、邮编长度),抛出
IllegalArgumentException - 不提供默认值逻辑(如自动补全“中国”),那是领域服务或工厂的责任,不是值对象本体的职责
重写 equals 和 hashCode,仅基于属性值
Java 默认的 equals 比较引用,而值对象要求“属性相同即相等”。必须显式重写这两个方法,且只依赖 final 字段。
- 推荐用 IDE 自动生成(IntelliJ / Eclipse),或使用 Lombok 的
@Value注解(它会自动加final、构造器、equals、hashCode、toString) - 注意:若含集合字段,确保其
equals行为也符合预期(如unmodifiableList的equals是按元素顺序逐个比较) - 不要在
hashCode中引入随机数、时间戳等不稳定因子
行为方法只返回新对象,不修改自身
值对象可以有业务方法(如地址标准化、金额相加),但所有方法都不能改变当前实例状态,而是返回一个新实例。
- 例如
withStreet(String newStreet)应返回新的Address,而非修改this.street - 避免命名误导:不用
updateStreet()或setCity()这类动词,优先用withXxx()或plus()等表明“产出新值” - 若方法纯属查询(如
isDomestic()),可安全调用,不违反不可变性
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











