1. 概述
在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。例如下面程序展示的情况:
class Student { private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = stu1; stu1.setNumber(54321); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); } }结果:>学生1:54321 >学生2:54321
为什么改变学生2的学号,学生1的学号也发生了变化呢?
原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,这样,stu1和stu2指向内存堆中同一个对象。如图: 那么,怎么能干干净净清清楚楚地复制一个对象呢。在 Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求有很多途径, (1)将A对象的值分别通过set方法加入B对象中; (2)通过重写java.lang.Object类中的方法clone(); (3)通过org.apache.commons中的工具类BeanUtils和PropertyUtils进行对象复制; (4)通过JSON之间的转换,进行对象克隆。 (5)通过序列化实现对象的复制2、将A对象的值分别通过set方法加入B对象中
对属性逐个赋值,本实例为了演示简单就设置了一个属性:
Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = new Student(); stu2.setNumber(stu1.getNumber());1234
我们发现,属性少对属性逐个赋值还挺方便,但是属性多时,就需要一直get、set了,非常麻烦。
3、重写java.lang.Object类中的方法clone()
先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
3.1 浅克隆
一般步骤:
被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法) 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
class Student implements Cloneable{ private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } [@Override](https://my.oschina.net/u/1162528) public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return stu; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); stu2.setNumber(54321); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); } }
结果:
学生1:12345
学生2:12345 学生1:12345 学生2:54321
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
3.2 深克隆
package abc; class Address implements Cloneable { private String add; public String getAdd() { return add; } public void setAdd(String add) { this.add = add; } [@Override](https://my.oschina.net/u/1162528) public Object clone() { Address addr = null; try{ addr = (Address)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return addr; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } [@Override](https://my.oschina.net/u/1162528) public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); //浅复制 }catch(CloneNotSupportedException e) { e.printStackTrace(); } stu.addr = (Address)addr.clone(); //深度复制 return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); addr.setAdd("西湖区"); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市 学生1:123,地址:西湖区 学生2:123,地址:西湖区
怎么两个学生的地址都改变了?
原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。 为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:package abc; class Address implements Cloneable { private String add; public String getAdd() { return add; } public void setAdd(String add) { this.add = add; } [@Override](https://my.oschina.net/u/1162528) public Object clone() { Address addr = null; try{ addr = (Address)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return addr; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } [@Override](https://my.oschina.net/u/1162528) public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); //浅复制 }catch(CloneNotSupportedException e) { e.printStackTrace(); } stu.addr = (Address)addr.clone(); //深度复制 return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); addr.setAdd("西湖区"); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市 学生1:123,地址:西湖区 学生2:123,地址:杭州市
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
(如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。)
4、工具类BeanUtils和PropertyUtils进行对象复制
Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = new Student(); BeanUtils.copyProperties(stu2,stu1);
这种写法无论多少种属性都只需要一行代码搞定,很方便吧!除BeanUtils外还有一个名为PropertyUtils的工具类,它也提供copyProperties()方法,作用与BeanUtils的同名方法十分相似,主要的区别在于BeanUtils提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而PropertyUtils不支持这个功能,但是速度会更快一些。在实际开发中,BeanUtils使用更普遍一点,犯错的风险更低一点。
5、通过JSON转换实现深克隆
用fastjson从Object转成json,然后转回object,本质上是反射:
private Object deepCopyByJson(Object obj) { String json = JSON.toJSONString(obj); return JSON.parseObject(json, Object.class);}
6.通过序列化实现对象的复制
序列化与反序列化,使用SerializationUtils的clone(Object obj)方法,要求拷贝的对象实现了Serializable,Map不行,使用HashMap即可。
SerializationUtils该类为序列化工具类,也是lang包下的工具,主要用于序列化操作,同时提供对象克隆接口。this.para = (Map) SerializationUtils.clone((HashMap )po.getPara());
7.个人意见
在对性能要求不高的情况下,为了代码的简洁,可以采取fastjson或者Serialization这两种方式;
对性能要求较高,可以通过set方法自己写对应业务的工具类完成。