java对象clone()方法

原文链接: https://blog.csdn.net/54powerman/article/details/64920431?locationNum=6&fps=1

深入解析地址:https://www.cnblogs.com/Qian123/p/5710533.html#_label3

java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的:

  1. @Test

  2. public void testassign(){

  3. Person p1=new Person();

  4. p1.setAge(31);

  5. p1.setName("Peter");

  6.  
  7. Person p2=p1;

  8. System.out.println(p1==p2);//true

  9. }

如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,而互不影响,就需要用到java中对象的复制,如原生的clone()方法。

如何进行对象克隆

Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:

① 实现Cloneable接口,这是一个标记接口,自身没有方法。 
② 覆盖clone()方法,可见性提升为public。

 
  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. return super.clone();

  9. }

  10. }

  11.  
  12. @Test

  13. public void testShallowCopy() throws Exception{

  14. Person p1=new Person();

  15. p1.setAge(31);

  16. p1.setName("Peter");

  17.  
  18. Person p2=(Person) p1.clone();

  19. System.out.println(p1==p2);//false

  20. p2.setName("Jacky");

  21. System.out.println("p1="+p1);//p1=Person [name=Peter, age=31]

  22. System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31]

  23. }

该测试用例只有两个基本类型的成员,测试达到目的了。

事情貌似没有这么简单,为Person增加一个Address类的成员:

 
  1. @Data

  2. public class Address {

  3. private String type;

  4. private String value;

  5. }

再来测试,问题来了。

 
  1. @Test

  2. public void testShallowCopy() throws Exception{

  3. Address address=new Address();

  4. address.setType("Home");

  5. address.setValue("北京");

  6.  
  7. Person p1=new Person();

  8. p1.setAge(31);

  9. p1.setName("Peter");

  10. p1.setAddress(address);

  11.  
  12. Person p2=(Person) p1.clone();

  13. System.out.println(p1==p2);//false

  14.  
  15. p2.getAddress().setType("Office");

  16. System.out.println("p1="+p1);

  17. System.out.println("p2="+p2);

  18. }

查看输出:

 
  1. false

  2. p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

  3. p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

遇到了点麻烦,只修改了p2的地址类型,两个地址类型都变成了Office。

浅拷贝和深拷贝

前面实例中是浅拷贝和深拷贝的典型用例。

浅拷贝:被复制对象的所有值属性都含有与原来对象的相同,而所有的对象引用属性仍然指向原来的对象。

深拷贝:在浅拷贝的基础上,所有引用其他对象的变量也进行了clone,并指向被复制过的新对象。

也就是说,一个默认的clone()方法实现机制,仍然是赋值。

如果一个被复制的属性都是基本类型,那么只需要实现当前类的cloneable机制就可以了,此为浅拷贝。

如果被复制对象的属性包含其他实体类对象引用,那么这些实体类对象都需要实现cloneable接口并覆盖clone()方法。

 
  1. @Data

  2. public class Address implements Cloneable {

  3. private String type;

  4. private String value;

  5.  
  6. @Override

  7. protected Object clone() throws CloneNotSupportedException {

  8. return super.clone();

  9. }

  10. }

这样还不够,Person的clone()需要显式地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. }

重新跑前面的测试用例:

 
  1. false

  2. p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))

  3. p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

clone方式深拷贝小结

① 如果有一个非原生成员,如自定义对象的成员,那么就需要:

  • 该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为public可见。
  • 同时,修改被复制类的clone()方法,增加成员的克隆逻辑。

② 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。

与对象成员不同,继承关系中的clone不需要被复制类的clone()做多余的工作。

一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。

前面的实例还可以接受,如果有N个对象成员,有M层继承关系,就会很麻烦。

利用序列化实现深拷贝

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

要寻找可靠的,简单的方法,序列化就是一种途径。

  • 被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。

  • 实现自己的deepClone方法,将this写入流,再读出来。俗称:冷冻-解冻。

 
  1. @Data

  2. public class Person implements Serializable {

  3. private String name;

  4. private Integer age;

  5. private Address address;

  6. public Person deepClone() {

  7. Person p2=null;

  8. Person p1=this;

  9. PipedOutputStream out=new PipedOutputStream();

  10. PipedInputStream in=new PipedInputStream();

  11. try {

  12. in.connect(out);

  13. } catch (IOException e) {

  14. e.printStackTrace();

  15. }

  16.  
  17. try(ObjectOutputStream bo=new ObjectOutputStream(out);

  18. ObjectInputStream bi=new ObjectInputStream(in);) {

  19. bo.writeObject(p1);

  20. p2=(Person) bi.readObject();

  21.  
  22. } catch (Exception e) {

  23. e.printStackTrace();

  24. }

  25. return p2;

  26. }

  27. }

原型工厂类

为了便于测试,也节省篇幅,封装一个工厂类。

公平起见,避免某些工具库使用缓存机制,使用原型方式工厂。

 
  1. public class PersonFactory{

  2. public static Person newPrototypeInstance(){

  3. Address address = new Address();

  4. address.setType("Home");

  5. address.setValue("北京");

  6.  
  7. Person p1 = new Person();

  8. p1.setAddress(address);

  9. p1.setAge(31);

  10. p1.setName("Peter");

  11. return p1;

  12. }

  13. }

利用Dozer拷贝对象

Dozer是一个Bean处理类库。

maven依赖

 
  1. net.sf.dozer

  2. dozer

  3. 5.5.1

测试用例:

 
  1. @Data

  2. public class Person {

  3. private String name;

  4. private Integer age;

  5. private Address address;

  6.  
  7. @Test

  8. public void testDozer() {

  9. Person p1=PersonFactory.newPrototypeInstance();

  10. Mapper mapper = new DozerBeanMapper();

  11. Person p2 = mapper.map(p1, Person.class);

  12. p2.getAddress().setType("Office");

  13. System.out.println("p1=" + p1);

  14. System.out.println("p2=" + p2);

  15. }

  16. }

  17.  
  18. @Data

  19. public class Address {

  20. private String type;

  21. private String value;

  22. }

输出:

 
  1. p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))

  2. p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

注意:在万次测试中dozer有一个很严重的问题,如果DozerBeanMapper对象在for循环中创建,效率(dozer:7358)降低近10倍。由于DozerBeanMapper是线程安全的,所以不应该每次都创建新的实例。可以自带的单例工厂DozerBeanMapperSingletonWrapper来创建mapper,或集成到spring中。

还有更暴力的,创建一个People类:

 
  1. @Data

  2. public class People {

  3. private String name;

  4. private String age;//这里已经不是Integer了

  5. private Address address;

  6.  
  7. @Test

  8. public void testDozer() {

  9. Person p1=PersonFactory.newPrototypeInstance();

  10. Mapper mapper = new DozerBeanMapper();

  11. People p2 = mapper.map(p1, People.class);

  12. p2.getAddress().setType("Office");

  13. System.out.println("p1=" + p1);

  14. System.out.println("p2=" + p2);

  15. }

  16. }

只要属性名相同,干~

继续蹂躏:

 
  1. @Data

  2. public class People {

  3. private String name;

  4. private String age;

  5. private Map address;//��

  6.  
  7. @Test

  8. public void testDozer() {

  9. Person p1=PersonFactory.newPrototypeInstance();

  10. Mapper mapper = new DozerBeanMapper();

  11. People p2 = mapper.map(p1, People.class);

  12. p2.getAddress().put("type", "Office");

  13. System.out.println("p1=" + p1);

  14. System.out.println("p2=" + p2);

  15. }

  16. }

利用Commons-BeanUtils复制对象

maven依赖

 
  1. commons-beanutils

  2. commons-beanutils

  3. 1.9.3

测试用例:

 
  1. @Data

  2. public class Person {

  3. private String name;

  4. private String age;

  5. private Address address;

  6.  
  7. @Test

  8. public void testCommonsBeanUtils(){

  9. Person p1=PersonFactory.newPrototypeInstance();

  10. try {

  11. Person p2=(Person) BeanUtils.cloneBean(p1);

  12. System.out.println("p1=" + p1);

  13. p2.getAddress().setType("Office");

  14. System.out.println("p2=" + p2);

  15. } catch (Exception e) {

  16. e.printStackTrace();

  17. }

  18. }

  19. }

利用cglib复制对象

maven依赖:

 
  1. cglib

  2. cglib

  3. 3.2.4

测试用例:

 
  1. @Test

  2. public void testCglib(){

  3. Person p1=PersonFactory.newPrototypeInstance();

  4. BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false);

  5. Person p2=new Person();

  6. beanCopier.copy(p1, p2,null);

  7. p2.getAddress().setType("Office");

  8. System.out.println("p1=" + p1);

  9. System.out.println("p2=" + p2);

  10. }

结果大跌眼镜,cglib这么牛x,居然是浅拷贝。不过cglib提供了扩展能力:

 
  1. @Test

  2. public void testCglib(){

  3. Person p1=PersonFactory.newPrototypeInstance();

  4. BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);

  5. Person p2=new Person();

  6. beanCopier.copy(p1, p2, new Converter(){

  7. @Override

  8. public Object convert(Object value, Class target, Object context) {

  9. if(target.isSynthetic()){

  10. BeanCopier.create(target, target, true).copy(value, value, this);

  11. }

  12. return value;

  13. }

  14. });

  15. p2.getAddress().setType("Office");

  16. System.out.println("p1=" + p1);

  17. System.out.println("p2=" + p2);

  18. }

Orika复制对象

orika的作用不仅仅在于处理bean拷贝,更擅长各种类型之间的转换。

maven依赖:

 
  1. ma.glasnost.orika

  2. orika-core

  3. 1.5.0

测试用例:

 
  1. @Test

  2. public void testOrika() {

  3. MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

  4.  
  5. mapperFactory.classMap(Person.class, Person.class)

  6. .byDefault()

  7. .register();

  8. ConverterFactory converterFactory = mapperFactory.getConverterFactory();

  9. MapperFacade mapper = mapperFactory.getMapperFacade();

  10.  
  11. Person p1=PersonFactory.newPrototypeInstance();

  12. Person p2 = mapper.map(p1, Person.class);

  13. System.out.println("p1=" + p1);

  14. p2.getAddress().setType("Office");

  15. System.out.println("p2=" + p2);

  16. }

Spring BeanUtils复制对象

给Spring个面子,貌似它不支持深拷贝。

 
  1. Person p1=PersonFactory.newPrototypeInstance();

  2. Person p2 = new Person();

  3. Person p2 = (Person) BeanUtils.cloneBean(p1);

  4. //BeanUtils.copyProperties(p2, p1);//这个更没戏

深拷贝性能对比

 
  1. @Test

  2. public void testBatchDozer(){

  3. Long start=System.currentTimeMillis();

  4. Mapper mapper = new DozerBeanMapper();

  5. for(int i=0;i<10000;i++){

  6. Person p1=PersonFactory.newPrototypeInstance();

  7. Person p2 = mapper.map(p1, Person.class);

  8. }

  9. System.out.println("dozer:"+(System.currentTimeMillis()-start));

  10. //dozer:721

  11. }

  12. @Test

  13. public void testBatchBeanUtils(){

  14. Long start=System.currentTimeMillis();

  15. for(int i=0;i<10000;i++){

  16. Person p1=PersonFactory.newPrototypeInstance();

  17. try {

  18. Person p2=(Person) BeanUtils.cloneBean(p1);

  19. } catch (Exception e) {

  20. e.printStackTrace();

  21. }

  22. }

  23. System.out.println("commons-beanutils:"+(System.currentTimeMillis()-start));

  24. //commons-beanutils:229

  25. }

  26. @Test

  27. public void testBatchCglib(){

  28. Long start=System.currentTimeMillis();

  29. for(int i=0;i<10000;i++){

  30. Person p1=PersonFactory.newPrototypeInstance();

  31. BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);

  32. Person p2=new Person();

  33. beanCopier.copy(p1, p2, new Converter(){

  34. @Override

  35. public Object convert(Object value, Class target, Object context) {

  36. if(target.isSynthetic()){

  37. BeanCopier.create(target, target, true).copy(value, value, this);

  38. }

  39. return value;

  40. }

  41. });

  42. }

  43. System.out.println("cglib:"+(System.currentTimeMillis()-start));

  44. //cglib:133

  45. }

  46. @Test

  47. public void testBatchSerial(){

  48. Long start=System.currentTimeMillis();

  49. for(int i=0;i<10000;i++){

  50. Person p1=PersonFactory.newPrototypeInstance();

  51. Person p2=p1.deepClone();

  52. }

  53. System.out.println("serializable:"+(System.currentTimeMillis()-start));

  54. //serializable:687

  55. }

  56. @Test

  57. public void testBatchOrika() {

  58. MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

  59.  
  60. mapperFactory.classMap(Person.class, Person.class)

  61. .field("name", "name")

  62. .byDefault()

  63. .register();

  64. ConverterFactory converterFactory = mapperFactory.getConverterFactory();

  65. MapperFacade mapper = mapperFactory.getMapperFacade();

  66.  
  67. Long start=System.currentTimeMillis();

  68. for(int i=0;i<10000;i++){

  69. Person p1=PersonFactory.newPrototypeInstance();

  70. Person p2 = mapper.map(p1, Person.class);

  71. }

  72. System.out.println("orika:"+(System.currentTimeMillis()-start));

  73. //orika:83

  74. }

  75.  
  76. @Test

  77. public void testBatchClone(){

  78. Long start=System.currentTimeMillis();

  79. for(int i=0;i<10000;i++){

  80. Person p1=PersonFactory.newPrototypeInstance();

  81. try {

  82. Person p2=(Person) p1.clone();

  83. } catch (CloneNotSupportedException e) {

  84. e.printStackTrace();

  85. }

  86. }

  87. System.out.println("clone:"+(System.currentTimeMillis()-start));

  88. //clone:8

  89. }

(10k)性能比较:

 
  1. //dozer:721

  2. //commons-beanutils:229

  3. //cglib:133

  4. //serializable:687

  5. //orika:83

  6. //clone:8

深拷贝总结

原生的clone效率无疑是最高的,用脚趾头都能想到。

偶尔用一次,用哪个都问题都不大。

一般性能要求稍高的应用场景,cglib和orika完全可以接受。

另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。

你可能感兴趣的:(Effective,java)