别再被Java深拷贝坑了!一文告诉你如何快速高效实现

76 阅读2分钟

很多文章列举了实现深拷贝的方式,比如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);
}