在我们的日常开发中可能会暂时需要保存某个对象的拷贝,如果new一个新对象再把值一个一个set进去也不是不可以,但总感觉很蠢,所以Java为我们提供了clone方法,使用这个方法拷贝也就是我们要提到的浅克隆了。
浅克隆
浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
浅克隆要求克隆的类必须实现Cloneable接口,Cloneable接口与Serializable接口一样也是标记接口,只有实现了这个接口的类才能调用clone方法。clone方法是Object类的本地protected方法,因此还需重写这个方法,把修饰符改为public然后调用Object的clone方法就好了。
例如要克隆的Student类:
class Student implements Serializable,Cloneable{
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private String gender;
public Student(String name, Integer age, String gender, Score score) {
this.name = name;
this.age = age;
this.gender = gender;
this.score = score;
}
public Score getScore() {
return score;
}
public void setScore(Score score) {
this.score = score;
}
private Score score;
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", score=" + score +
'}';
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Score {
private Integer mathematics;
private Integer Chinese;
public Score(Integer mathematics, Integer chinese) {
this.mathematics = mathematics;
Chinese = chinese;
}
public Integer getMathematics() {
return mathematics;
}
public void setMathematics(Integer mathematics) {
this.mathematics = mathematics;
}
public Integer getChinese() {
return Chinese;
}
public void setChinese(Integer chinese) {
Chinese = chinese;
}
@Override
public String toString() {
return "Score{" +
"mathematics=" + mathematics +
", Chinese=" + Chinese +
'}';
}
}
测试程序及输出结果:
//测试程序
public static void main(String[] args) {
Score score = new Score(93,96);
Student student = new Student("赵彦祖",18,"男",score);
System.out.println("原始对象:"+student.toString());
try {
Student studentCopy = (Student) student.clone();
System.out.println("拷贝对象:"+studentCopy.toString());
System.out.println("比较引用类型地址是否相同:"+(student.getScore()==studentCopy.getScore()));
System.out.println("********修改拷贝对象********");
studentCopy.setName("charles");
studentCopy.setAge(20);
studentCopy.setGender("unknow");
Score anotherScore = studentCopy.getScore();
anotherScore.setChinese(59);
anotherScore.setMathematics(37);
studentCopy.setScore(anotherScore);
System.out.println("原始对象:"+student.toString());
System.out.println("拷贝对象:"+studentCopy.toString());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
//输出结果
原始对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
拷贝对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
比较引用类型地址是否相同:true
********修改拷贝对象********
原始对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=37, Chinese=59}}
拷贝对象:Student{name='charles', age=20, gender='unknow', score=Score{mathematics=37, Chinese=59}}
可以看出来浅克隆只是克隆了基本类型的值,String类型的值和引用指向的地址(即原始对象和拷贝对象的Score引用指向统同一地址),因此修改拷贝后的对象的Score对象同样会影响到原始对象。无论如何这都不是我们想要的,这时候我们就需要使用深克隆。
深克隆
深克隆不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
拿上面的例子来说,使用深克隆之后拷贝对象的Score引用指向了一个新的地址,这个新的地址存放的是原始对象的Score对象的拷贝。深克隆是通过序列化和反序列化实现的,序列化和反序列化可以看这篇文章。此处需要注意被序列化的类要实现Serializable接口,类中属性如果有引用类型,则该类同样要实现Serializable。例如在此例中Student类和Score类都需要实现Serializable接口,有任何一个没有实现都会抛出NotSerializableException异常。如果Score类中也有引用类型的属性,则该引用类型同样要实现Serializable接口。
使用深克隆克隆Student类:
//测试程序
public static void main(String[] args) {
Score score = new Score(93,96);
Student student = new Student("赵彦祖",18,"男",score);
System.out.println("原始对象:"+student.toString());
Student studentCopy = clone(student);
System.out.println("拷贝对象:"+studentCopy.toString());
System.out.println("比较引用类型地址是否相同:"+(student.getScore()==studentCopy.getScore()));
System.out.println("********修改拷贝对象********");
studentCopy.setName("charles");
studentCopy.setAge(20);
studentCopy.setGender("unknow");
Score anotherScore = studentCopy.getScore();
anotherScore.setChinese(59);
anotherScore.setMathematics(37);
System.out.println("原始对象:"+student.toString());
System.out.println("拷贝对象:"+studentCopy.toString());
}
//深克隆方法
public static <T>T clone(T object) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
T t = null;
try {
oos = new ObjectOutputStream(bos);
oos.writeObject(object);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
t = (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
ois.close();
bis.close();
oos.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return t;
}
//输出结果
原始对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
拷贝对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
比较引用类型地址是否相同:false
********修改拷贝对象********
原始对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
拷贝对象:Student{name='charles', age=20, gender='unknow', score=Score{mathematics=37, Chinese=59}}
可以看到深克隆后原始对象的Score引用与拷贝对象的Score引用指向的不是同一地址了,当然修改拷贝对象的Score属性也不会影响到原始对象。