今天收到经理的一封Email,里面是关于对象拷贝的东西,实例代码是C#的,大概看了下,基本还是能看明白!下来觉得还是总结下Java中的对象克隆的知识点以便日后查阅。
对象克隆
当我们拷贝一个变量时,原始变量与拷贝变量引用同一个对象,这就是说,改变一个变量所引用的对象将会对另一个变量产生影响。
Employee类:
/**
* @author XzZhao
*/
public class Employee {
private String name;
private double salary;
private Date hireDay;
public Employee(String name, double salary) {
super();
this.name = name;
this.salary = salary;
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
/******* get set *********/
}Test:
public static void main(String[] args) {
Employee original = new Employee("John", 50000);
Employee copy = original;
copy.raiseSalary(10);
System.out.println(original.getSalary()); // original changed
}
如果创建一个对象的新的copy,它的最初状态与original一样,但以后将可以各自改变各自的状态,那就需要clone方法。clone是Object类的一个proteced方法,在代码中不能直接调用它。只有Employee类才能够克隆Employee对象。如果对象中的所有数据域都属于数值或基本类型,这样拷贝域没有任何问题。但是,如果在对象中包含了子对象的引用,拷贝的结果会使得两个域引用同一个子对象,因此原始对象与克隆对象共享这部分信息。
浅拷贝
Object类的clone方法克隆对象,默认的克隆操作是浅拷贝,它并没有克隆包含在对象中的内部对象。
数据域 操作 原始对象和克隆对象相互影响
基本类型 将各个域进行拷贝 不会
引用类型 引用同一个子对象 分情况
Employee类:
/**
* @author XzZhao
*/
public class Employee implements Cloneable {
private String name;
private double salary;
private Date hireDay;
public Employee(String name, double salary) {
super();
this.name = name;
this.salary = salary;
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
/**
* 浅拷贝
*
* @see java.lang.Object#clone()
*/
@Override
public Employee clone() throws CloneNotSupportedException {
return (Employee) super.clone();
}
/******* get set *********/
}Test:
public static void main(String[] args) {
try {
Employee original = new Employee("John", 50000);
Employee copy = original.clone();
copy.raiseSalary(10);
System.out.println(original.getSalary());// original unchanged
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
如果进行浅拷贝会发生什么?这要根据具体情况而定。如果原始对象与浅克隆对象共享的子对象是不可变的,将不会产生任何问题。也确是存在这种情形。例如,子对象属于String类这样的不允许改变的类;也有可能子对象在其生命周期内不会发生变化,既没有更改它们的方法,也没有创建对它引用的方法。
然而,更常见的情况是子对象可变,因此必须重新定义clone方法,以便实现克隆子对象的深拷贝。Employee类中的hireDay域属于Date类,这就是一个可变的子对象。
深拷贝
上面的clone方法并没有在Object.clone 提供的浅拷贝基础上增加任何新的功能,而只是将它声明为public。为了实现深拷贝,必须克隆所有可变的实例域。
数据域 操作 原始对象和克隆对象相互影响
基本类型 将各个域进行拷贝 不会
引用类型 克隆所有可变的实例域 不会
Employee类:
/**
* @author XzZhao
*/
public class Employee implements Cloneable {
private String name;
private double salary;
private Date hireDay;
public Employee(String name, double salary) {
super();
this.name = name;
this.salary = salary;
this.hireDay = new Date();
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
/**
* 深拷贝
*
* @see java.lang.Object#clone()
*/
@Override
public Employee clone() throws CloneNotSupportedException {
Employee cloned = (Employee) super.clone();
// clone mutable
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
/******* get set *********/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Date getHireDay() {
return hireDay;
}
public void setHireDay(int year, int month, int day) {
Date newHire = new GregorianCalendar(year, month - 1, day).getTime();
this.hireDay.setTime(newHire.getTime());
}
@Override
public String toString() {
return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
}
}
Test:
public static void main(String[] args) {
try {
Employee original = new Employee("John", 50000);
original.setHireDay(2014, 1, 1);
Employee copy = original.clone();
copy.raiseSalary(10);
copy.setHireDay(2015, 12, 1);
System.out.println("original=" + original);
System.out.println("copy=" + copy);
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
上面的实例克隆了一个Employee对象,然后,调用两个改变域值的方法。raiseSalary方法改变了salary域值,setHireDay方法改变了hireDay域的状态。由于实现了深拷贝,所以对这两个域值的改变并没有影响原始数据。
使用序列号深拷贝
序列号机制提供了一种克隆对象的简便途径,只要对应的类是可序列号的即可。直接将对象序列化到输出流中,然后再将其读回。这样产生的新对象是对现有对象的一个深拷贝,但是尽管这个方法很灵活,但是它通常会比显示地构建新对象并复制或克隆数据域的克隆方法慢得多。
SerialCloneable类:
/**
* @author XzZhao
*/
public class SerialCloneable implements Cloneable, Serializable {
@Override
public Object clone() throws CloneNotSupportedException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
ObjectOutputStream out = new ObjectOutputStream(bout);
out.writeObject(this);
out.close();
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin);
Object ret = in.readObject();
in.close();
return ret;
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
Employee类:
/**
* @author XzZhao
*/
public class Employee extends SerialCloneable {
private String name;
private double salary;
private final Date hireDay;
public Employee(String name, double salary) {
super();
this.name = name;
this.salary = salary;
this.hireDay = new Date();
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
/******* get set *********/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Date getHireDay() {
return hireDay;
}
public void setHireDay(int year, int month, int day) {
Date newHire = new GregorianCalendar(year, month - 1, day).getTime();
this.hireDay.setTime(newHire.getTime());
}
@Override
public String toString() {
return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
}
}
Test:
public static void main(String[] args) {
try {
Employee original = new Employee("John", 50000);
original.setHireDay(2014, 1, 1);
Employee copy = (Employee) original.clone();
copy.raiseSalary(10);
copy.setHireDay(2015, 12, 1);
System.out.println("original=" + original);
System.out.println("copy=" + copy);
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
小结
当我们进行对象克隆,都需要作出下列判断:
是否不应该使用clone。
默认的clone方法是否满足要求。
默认的clone方法是否能够通过调用可变子对象的clone得到修补。
实际上,大多数是第一种情况,如果要使用clone方法,类必须:
实现Cloneable接口。
使用public方法修饰符重新定义clone方法。