1为什么要克隆?
克隆的对象可能已经包含一些已经修改过的属性,保留着你想克隆对象的值,而new出来的对象的属性全是一个新的对象,对应的属性没有值,所以我们还要重新给这个对象赋值。
即当需要一个新的对象来保存当前对象的“状态”就靠clone方法了,那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。
克隆究竟是什么?
先来回忆为一个包含对象引用的变量建立副本时会发生什么 。原变量和副本都是同一个对象的引用 ( 见图 6 - 1 )。这说明 ,任何一个变量改变都会影响另一个变量。
Employee original = new Employee ( " John Public " , 50000 ) ;
Employee copy = original ;
copy . raiseSalary ( lO ) ; // oops - also changed original
上述 original和 copy两个对象引用指向的是同一个对象
如果希望 copy 是一个新对象, 它的初始状态与 original相同 , 但是之后它们各自会有自
己不同的状态 , 这种情况下就可以使用 clone 方法。
Employee copy = original.clone() ;
copy . raiseSalary ( lO ) ; / / OK original unchanged
不过并没有这么简单。clone方法是Object的一个protected方法,这说明你的代码不能直接调用这个方法。只有Employee类可以克隆Employee对象。这个限制是有原因的。想想看Object类如何实现clone。它对于这个对象一无所知,所以只能逐个域的拷贝地进行拷贝。如果对象中所有的数据域或者其他基本类型,拷贝这些域没有任何问题,但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来原对象和克隆的对象仍然会共享一些信息
为了更直观地说明这个问题, 考虑第4 章介绍过的 Employee 类。 图 6 - 2 显示了使用Object 类的 clone 方法克隆这样一个 Employee 对象会发生什么 。 可以看到 , 默认的克隆操作是 “ 浅拷贝 ” , 并没有克隆对象中引用的其他对象。( 这个图显示了一个共享的 Date 对象。 出于某种原因 ( 稍后就会解释这个原因 ), 这个例子使用了 Employee 类的老版本 , 其中的雇用日期仍用 Date 表示 。
2浅拷贝的潜在危害
浅拷贝会有什么问题吗?
这要看具体情况。
如果原对象和浅克隆对象共享的子对象是不可变的 , 那么这种共享就是安全的
如果子对象属于一个不可变的类,如String,就是这种情况。或者在对象的生命期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的作用,这种情况下同样是安全的。
不过,通常子对象都是可变的,必须重新定义clone方法来建立一个浅拷贝,同时克隆所有子对象。 在这个例子中 , hireDay域是一个 Date , 这是可变的,所以它也需要克隆 。( 出于这个原因 , 这个例子使用 Date 类型的域而不是 LocalDate 来展示克隆过程。 如果 hireDay是不可变的 LocalDate 类的一个实例,就无需我们做任何处理了 )
对于每一个类,需要确定:
1.默认的clone方法是否满足要求
2.是否可以在可变的子对象上调用clone来修补默认的clone方法
3.是否不该使用clone方法
3如何实现拷贝呢?
实际上第 3 个选项是默认选项。 如果选择第1 项或第 2 项 , 类必须 :
1 ) 实现 Cloneable 接口 ;
2 ) 重新定义 clone 方法 , 并指定 public 访问修饰符 。
4那么如何做到深拷贝呢?
很简单就是在在实现Clonable接口之后,自定义的clone方法中,调用引用对象的clone方法,这样就避免了Object.clone方法默认的对于引用对象的浅拷贝
看个例子很快就懂了:
package com.interview.xsj.cloneexample;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* @author xsj
* @create 2021-07-13 17:31
*/
public class CloneTest {
public static void main(String[] args) {
try{
Employee original = new Employee("John Q. Public", 50000);
original.setHireDay(2000, 1, 1);
Employee copy = original.clone();
copy.raiseSalary(10);
copy.setHireDay(2002, 12, 31);
System.out.println("original=" + original);
System.out.println("copy=" + copy);
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
}
}
class Employee implements Cloneable{
private String name;
private double salary;
private Date hireDay;
public Employee(String n, double s)
{
name = n;
salary = s;
hireDay = new Date();
}
public Employee clone()throws CloneNotSupportedException{
Employee cloned=(Employee)super.clone();
cloned.hireDay=(Date)hireDay.clone();
return cloned;
}
/**
* Set the hire day to a given date.
* @param year the year of the hire day
* @param month the month of the hire day
* @param day the day of the hire day
*/
public void setHireDay(int year, int month, int day)
{
Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
// Example of instance field mutation
hireDay.setTime(newHireDay.getTime());
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public String toString()
{
return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
}
}
核心就在于clone方法中的这一句
从结果可以看到引用对象hireDay修改了
5深拷贝与浅拷贝总结
两者的区别:就是关于需要clone的对象中如果包含引用对象时,是否将引用对象也进行一个clone(重新在堆空间中创建一个对象)
- 浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉。(仅仅拷贝对象本身,而不拷贝对象包含的引用指向的对象)
- 深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容,此为深拷⻉。(不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象)
参考:Java核心技术卷 一