深拷贝和浅拷贝工具整理

前几天写代码的时候,需要拷贝对象,就使用了org.apache.commons.beanutils.BeanUtilsBeanUtils.copyProperties(Object dest, Object orig)拷贝对象,后面又修改了新对象的属性,就导致原对象也被修改了,仔细一研究才发现这个工具只是进行了浅拷贝。索性整理一下现在比较常用的一些深拷贝和浅拷贝工具。

我的博客:深拷贝和浅拷贝工具

深拷贝

1. Orika的MapperFactory

Orika底层采用了javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件,因此在速度上比使用反射进行赋值会快很多。线程安全,可以使用单例。推荐!

首先引入依赖:

<dependency>
  <groupId>ma.glasnost.orika</groupId>
  <artifactId>orika-core</artifactId>
  <version>1.5.2</version>
</dependency>

可以拷贝单个对象,也可以拷贝列表,这里只介绍单个对象的拷贝:

  • 直接克隆对象

    克隆的对象可以不同,深拷贝两个对象相同的属性,跳过不同的属性。

    @Test
    public void orikaClone() {
      MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
      MapperFacade mapperFacade = mapperFactory.getMapperFacade();
    
      DemoObj fromObj = new DemoObj();
      fromObj.setStr("a");
      fromObj.setList(Lists.newArrayList("A", "B"));
      DemoObj toObj = mapperFacade.map(fromObj, DemoObj.class);
      toObj.setStr("b");
      toObj.getList().add("C");
      System.out.println(JSON.toJSONString(fromObj));
      System.out.println(JSON.toJSONString(toObj));
    }
    //输出:
    //{"list":["A","B"],"str":"a"}
    //{"list":["A","B","C"],"str":"b"}
    
  • 拷贝对象属性

    克隆的对象可以不同,深拷贝两个对象相同的属性,跳过不同的属性。

    @Test
    public void orikaCopy() {
      MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
      MapperFacade mapperFacade = mapperFactory.getMapperFacade();
    
      DemoObj fromObj = new DemoObj();
      fromObj.setStr("a");
      fromObj.setList(Lists.newArrayList("A", "B"));
      DemoObj toObj = new DemoObj();
      mapperFacade.map(fromObj, toObj);
      toObj.setStr("b");
      toObj.getList().add("C");
      System.out.println(JSON.toJSONString(fromObj));
      System.out.println(JSON.toJSONString(toObj));
    }
    //输出:
    //{"list":["A","B"],"str":"a"}
    //{"list":["A","B","C"],"str":"b"}
    

Dozer的DozerBeanMapper

dozer是一种JavaBean的映射工具,类似于apache的BeanUtils。但是dozer更强大,它可以灵活的处理复杂类型之间的映射。不但可以进行简单的属性映射、复杂的类型映射、双向映射、递归映射等,并且可以通过XML配置文件进行灵活的配置。 线程安全,可以使用单例,性能一般。

引入依赖:

<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.5.1</version>
</dependency>

深拷贝属性,对象可以不同,属性不同时跳过。

@Test
public void dozerCopy() {
  DozerBeanMapper dozer = new DozerBeanMapper();

  DemoObj fromObj = new DemoObj();
  fromObj.setStr("a");
  fromObj.setList(Lists.newArrayList("A", "B"));
  DemoObj2 toObj = new DemoObj2();
  dozer.map(fromObj, toObj);
  toObj.setStr("b");
  toObj.getList().add("C");
  System.out.println(JSON.toJSONString(fromObj));
  System.out.println(JSON.toJSONString(toObj));
}
//输出:
//{"list":["A","B"],"str":"a"}
//{"list":["A","B","C"],"str":"b"}

利用序列化实现深拷贝

利用输入输出流,将旧对象写入到新对象,实现拷贝。性能低,不推荐。

@Data
public static class DeepCloneDemo implements Serializable {
  String str;
  List<String> list;

  public DeepCloneDemo deepClone() {
    DeepCloneDemo to = null;
    DeepCloneDemo from = this;
    PipedOutputStream out = new PipedOutputStream();
    PipedInputStream in = new PipedInputStream();
    try {
      in.connect(out);
    } catch (IOException e) {
      e.printStackTrace();
    }

    try (ObjectOutputStream bo = new ObjectOutputStream(out);
         ObjectInputStream bi = new ObjectInputStream(in);) {
      bo.writeObject(from);
      to = (DeepCloneDemo) bi.readObject();

    } catch (Exception e) {
      e.printStackTrace();
    }
    return to;
  }
}

@Test
public void deepClone() {
  DeepCloneDemo from = new DeepCloneDemo();
  from.setStr("a");
  from.setList(Lists.newArrayList("A", "B"));
  DeepCloneDemo to = from.deepClone();
  to.setStr("b");
  to.getList().add("C");
  System.out.println(JSON.toJSONString(from));
  System.out.println(JSON.toJSONString(to));
}
//输出:
//{"list":["A","B"],"str":"a"}
//{"list":["A","B","C"],"str":"b"}

浅拷贝

1. apache的BeanUtils

所处的包:org.apache.commons.beanutils.BeanUtils

基于反射拷贝,提供了两个拷贝对象的方法:

  • BeanUtils.cloneBean(final Object bean)

    传入一个对象,浅拷贝生成一个新的对象。

    @Test
    public void apacheClone() throws Exception {
      DemoObj fromObj = new DemoObj();
      fromObj.setS("a");
      fromObj.setL(Lists.newArrayList("A", "B"));
      DemoObj toObj = (DemoObj) org.apache.commons.beanutils.BeanUtils.cloneBean(fromObj);
      toObj.setStr("b");
      toObj.getList().add("C");
      System.out.println(JSON.toJSONString(fromObj));
      System.out.println(JSON.toJSONString(toObj));
    }
    //输出:
    //{"list":["A","B","C"],"str":"a"}
    //{"list":["A","B","C"],"str":"b"}
    
  • BeanUtils.copyProperties(final Object dest, final Object orig)

    传入两个对象,浅拷贝相同的属性的值。两个对象的类型可以不一样,属性及属性类型相同即可,忽略不同的属性。

    @Test
    public void apacheCopy() throws Exception {
      DemoObj fromObj = new DemoObj();
      fromObj.setS("a");
      fromObj.setL(Lists.newArrayList("A", "B"));
      DemoObj toObj = new DemoObj();
      org.apache.commons.beanutils.BeanUtils.copyProperties(toObj, fromObj);
      toObj.setStr("b");
      toObj.getList().add("C");
      System.out.println(JSON.toJSONString(fromObj));
      System.out.println(JSON.toJSONString(toObj));
    }
    //输出:
    //{"list":["A","B","C"],"str":"a"}
    //{"list":["A","B","C"],"str":"b"}
    

2. springframework的BeanUtils

所处的包:org.springframework.beans.BeanUtils

提供了四个拷贝对象的方法,基本原理都一样,只不过是做了一些定制。这里只整理基本的拷贝方法:

  • BeanUtils.copyProperties(Object source, Object target)

    功能与apache的相似,需要注意的是,传入对象的顺序刚好相反,原对象在第一个,新对象在第二个

    @Test
    public void springCopy() {
      DemoObj fromObj = new DemoObj();
      fromObj.setStr("a");
      fromObj.setList(Lists.newArrayList("A", "B"));
      DemoObj toObj = new DemoObj();
      org.springframework.beans.BeanUtils.copyProperties(fromObj, toObj);
      toObj.setStr("b");
      toObj.getList().add("C");
      System.out.println(JSON.toJSONString(fromObj));
      System.out.println(JSON.toJSONString(toObj));
    }
    //输出:
    //{"list":["A","B","C"],"str":"a"}
    //{"list":["A","B","C"],"str":"b"}
    

版权声明:本文为qq_22795957原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。