Java对象—复制Clone

克隆条件

  • 实现Clonyable接口(标记接口,自身没有方法。)
  • 覆盖Object的clone()方法,默认可见性为protected,需要提升至public。

    浅拷贝

  • 对基本类型进行值传递,对数据类型进行引用传递。

  • 不对clone()改造,只提升可见性是浅拷贝。
  • 被复制对象若有引用对象属性,克隆后的对象的引用属性仍然指向被克隆对象。默认的clone方法仍然是赋值。

    深拷贝

  • 对基本类型值传递,对引用类型创建一个新的对象,并复制内容

  • 需要引用类型属性的类也要实现clone。

    1. @Data
    2. public class Address implements Cloneable {
    3. private String type;
    4. private String value;
    5. @Override
    6. protected Object clone() throws CloneNotSupportedException {
    7. return super.clone();
    8. }
    9. }
  • 此外,被克隆对象还需要显事的clone其引用对象。

    1. @Data
    2. public class Person implements Cloneable {
    3. private String name;
    4. private Integer age;
    5. private Address address;
    6. @Override
    7. protected Object clone() throws CloneNotSupportedException {
    8. Object obj=super.clone();
    9. Address a=((Person)obj).getAddress();
    10. ((Person)obj).setAddress((Address) a.clone());
    11. return obj;
    12. }
    13. }

    利用序列化实现深拷贝

  • clone机制不是强类型的限制,比如实现了Cloneable并没有强制继承链上的对象也实现;也没有强制要求覆盖clone()方法。因此编码过程中比较容易忽略其中一个环节,对于复杂的项目排查就是困难了。

  • 要寻找可靠的,简单的方法,序列化就是一种途径。
    • 被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。
    • 实现自己的deepClone方法,将this写入流,再读出来。俗称:冷冻-解冻。

      Dozer拷贝

      1. <dependency>
      2. <groupId>net.sf.dozer</groupId>
      3. <artifactId>dozer</artifactId>
      4. <version>5.5.1</version>
      5. </dependency>
      @Test
      public void testDozer() {
      Person p1=PersonFactory.newPrototypeInstance();
         Mapper mapper = new DozerBeanMapper();
         Person p2 = mapper.map(p1, Person.class);
         p2.getAddress().setType("Office");
         System.out.println("p1=" + p1);
         System.out.println("p2=" + p2);
      }
      

      Commons-BeanUtils复制对象

      <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.9.3</version>
      </dependency>
      
      @Test
      public void testCommonsBeanUtils(){
      Person p1=PersonFactory.newPrototypeInstance();
         try {
             Person p2=(Person) BeanUtils.cloneBean(p1);
             System.out.println("p1=" + p1);
             p2.getAddress().setType("Office");
             System.out.println("p2=" + p2);
         } catch (Exception e) {
             e.printStackTrace();
         }
      }
      

      cglib复制对象

      <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.2.4</version>
      </dependency>
      
      @Test
      public void testCglib(){
      Person p1=PersonFactory.newPrototypeInstance();
      BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false);
      Person p2=new Person();
      beanCopier.copy(p1, p2,null);
      p2.getAddress().setType("Office");
      System.out.println("p1=" + p1);
      System.out.println("p2=" + p2);
      }
      
      @Test
      public void testCglib(){
      Person p1=PersonFactory.newPrototypeInstance();
      BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
      Person p2=new Person();
      beanCopier.copy(p1, p2, new Converter(){
      @Override
      public Object convert(Object value, Class target, Object context) {
       if(target.isSynthetic()){
         BeanCopier.create(target, target, true).copy(value, value, this);
       }
       return value;
      }
      });
      p2.getAddress().setType("Office");
      System.out.println("p1=" + p1);
      System.out.println("p2=" + p2);
      }
      

      Orika复制对象

      <dependency>
      <groupId>ma.glasnost.orika</groupId>
      <artifactId>orika-core</artifactId>
      <version>1.5.0</version>
      </dependency>
      </dependencies>
      
      @Test
      public void testOrika() {
      MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
      mapperFactory.classMap(Person.class, Person.class)
      .byDefault()
      .register();
      ConverterFactory converterFactory = mapperFactory.getConverterFactory();
      MapperFacade mapper = mapperFactory.getMapperFacade();
      Person p1=PersonFactory.newPrototypeInstance();
      Person p2 = mapper.map(p1, Person.class);
      System.out.println("p1=" + p1);
      p2.getAddress().setType("Office");
      System.out.println("p2=" + p2);
      }
      

      Spring BeanUtils复制对象

      Person p1=PersonFactory.newPrototypeInstance();
      Person p2 = new Person();
      Person p2 = (Person) BeanUtils.cloneBean(p1);
      

      总结

      原生的clone效率无疑是最高的,用脚趾头都能想到。偶尔用一次,用哪个都问题都不大。一般性能要求稍高的应用场景,cglib和orika完全可以接受。另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。

参考