在Java中的不变对象 提到了在生成不变对象时,如果类中有引用类型的应用,那么在初始化传入参数和返回引用属性时,会通过引用类型的clone()
方法克隆一个新的对象,从而隔离属性的引用“逃逸”到不变对象之外,起到改变不变对象内部状态的结果。但是clone()
方法默认实现的是浅拷贝,如果存在多个嵌套对象的话,那需要考虑实现深拷贝。
浅拷贝
浅拷贝,只拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用对象。拷贝出来的新对象中的引用类型变量还是指向原来的对象。比如下面这个例子:
浅拷贝的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class Employee { private String SN; private String name; private Date birthday; public Employee(String SN, String name, Date birthday){ this.SN = SN; this.name = name; this.birthday = birthday; } public String getSN(){ return SN; } public String getName(){ return name; } public Date getBirthday(){ return birthday; }
public Object clone() throws CloneNotSupportedException { return (Employee)super.clone(); } public static void main(String[] args) throws InterruptedException { Employee emp = new Employee("123", "John", new Date()); Employee cloneEmp = (Employee)emp.clone(); System.out.println(cloneEmp.getBirthday()); Thread.sleep(1000); emp.getBirthday().setTime(System.currentTimeMillis()); System.out.println(cloneEmp.getBirthday()); Thread.sleep(1000); emp.getBirthday().setTime(System.currentTimeMillis()); System.out.println(cloneEmp.getBirthday()); } }
|
运行结果:
1 2 3
| Wed Aug 15 21:03:37 CST 2018 Wed Aug 15 21:03:38 CST 2018 Wed Aug 15 21:03:39 CST 2018
|
可以看到Employee.birthday
这个引用类型的变量并没有被克隆一个新的对象,而还是指向了原来的对象,所以“逃逸”到了Employee
对象外面,从而导致了可以在对象外部修改birthday
这个属性。为了防止这种情况,所以我们在拷贝带有引用类型属性的对象时,通常会实现深拷贝。
深拷贝
深拷贝,会拷贝对象的所有的值(包括引用类型的值),即使源对象发生任何改变,拷贝的值也不会变化。
深拷贝的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class Employee { private String SN; private String name; private Date birthday; public Employee(String SN, String name, Date birthday){ this.SN = SN; this.name = name; this.birthday = birthday; } public String getSN(){ return SN; } public String getName(){ return name; } public Date getBirthday(){ return birthday; }
public Object clone() throws CloneNotSupportedException { return (Employee)super.clone(); } public static void main(String[] args) throws InterruptedException { Employee emp = new Employee("123", "John", new Date()); Employee cloneEmp = (Employee)emp.clone(); System.out.println(cloneEmp.getBirthday()); Thread.sleep(1000); emp.getBirthday().setTime(System.currentTimeMillis()); System.out.println(cloneEmp.getBirthday()); Thread.sleep(1000); emp.getBirthday().setTime(System.currentTimeMillis()); System.out.println(cloneEmp.getBirthday()); } }
|
运行结果:
1 2 3
| Wed Aug 15 21:33:13 CST 2018 Wed Aug 15 21:33:13 CST 2018 Wed Aug 15 21:33:13 CST 2018
|
这里使用的是重载clone()
方法,手动对birthday
属性进行clone
,并赋值给新的Employee
对象。这是最简单也最清楚易懂的实现方式,但是有一个问题就是如果存在很多层的嵌套对象,列如:department.employee.address
这种嵌套对象的话,需要重载每一个引用的对象,增加代码量,而且容易遗忘和出错。
在面对多层克隆的时候,还可以通过序列化来实现深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public class Employee implements Serializable { private static final long serialVersionUID = 369385298572951L; private String SN; private String name; private Date birthday; public Employee(String SN, String name, Date birthday){ this.SN = SN; this.name = name; this.birthday = birthday; } public String getSN(){ return SN; } public String getName(){ return name; } public Date getBirthday(){ return birthday; } public Employee clone() { Employee emp = null; try{ ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); emp = (Employee) ois.readObject(); }catch (IOException e) { throw new RuntimeException(e); }catch (ClassNotFoundException e) { e.printStackTrace(); } return emp; } public static void main(String[] args) throws InterruptedException, CloneNotSupportedException { Employee emp = new Employee("123", "John", new Date()); Employee cloneEmp = (Employee)emp.clone(); System.out.println(cloneEmp.getBirthday()); Thread.sleep(1000); emp.getBirthday().setTime(System.currentTimeMillis()); System.out.println(cloneEmp.getBirthday()); Thread.sleep(1000); emp.getBirthday().setTime(System.currentTimeMillis()); System.out.println(cloneEmp.getBirthday()); } }
|
运行结果:
1 2 3
| Wed Aug 15 21:46:09 CST 2018 Wed Aug 15 21:46:09 CST 2018 Wed Aug 15 21:46:09 CST 2018
|
可以通过 Apache Commons Lang中的 SerializationUtils
来简化序列化深拷贝的过程:
1
| Employee emp1 = (Employee)SerializationUtils.clone(emp);
|