很多文章列举了实现深拷贝的方式,比如Java 语言自带的 clone 方法,各种 BeanUtils,序列化实现。
其实,这些实现都有问题。clone 方法存在严重缺陷,特别是涉及到继承时,Effective Java 列出了clone 方法的问题:性能、复杂性问题、拓展性问题,这里不赘述。
BeanUtils 等方法本质上是通过反射获取属性,通过 setter 设置值,其有性能问题,而且无法通过代码直观地看出值的传递过程。
序列化实现也有性能问题,同时需要对象支持序列化,而支持序列化不是对象必须实现的特性。
总的来说,以上种种实现都或多或少依赖于 JavaBean, 或者说在 JavaBean 上应用没出现过大问题,实际上 JavaBean 是一种典型的反模式,我们不应该被这种实现所局限。
解决方法 -- 手动实现
笔者写代码秉承一个原则,减少使用魔法。你想实现一个深拷贝方法,那么就去写这个方法,再写一个单元测试。在单元测试里可以断言对象没有null 属性,以避免新增字段后忘记实现 deepCopy 的问题。
以下代码改自官方示例:
@Test
void testHasNoNullFieldsOrProperties() {
Book book = new Book("title", Collections.singletonList(new Author()));
assertThat(book).usingRecursiveAssertion().hasNoNullFields();
}
class Book {
String title;
List<Author> authors;
Book(String title, List<Author> authors) {
this.title = title;
this.authors = authors;
}
}
class Author{}
自动化实现
使用 map-stuct 框架,可以编译期生成代码,其不仅支持 JavaBean,还支持 Builder 模式,同时可以自己拓展实现任何复杂的逻辑。
如果项目中不允许使用 map-struct,你可以自己本地做一个分支,手动复制代码。
@Mapper(mappingControl = DeepClone.class)
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
// @Mapping(source = "address.street", target = "address.street") // 基本不需要指定映射
Person personToPerson(Person person);
}