深入理解Java的浅克隆与深克隆
前言
克隆,即复制一个对象,该对象的属性与被复制的对象一致,如果不使用Object类中的clone方法实现克隆,可以自己new出一个对象,并对相应的属性进行数据,这样也能实现克隆的目的。
但当对象属性较多时,这样的克隆方式会比较麻烦,所以Object类中实现了clone方法,用于克隆对象。
Java中的克隆分为浅克隆与深克隆
一、实现克隆的方式
1.对象的类需要实现Cloneable接口
2.重写Object类中的clone()方法
3.根据重写的clone()方法得到想要的克隆结果,例如浅克隆与深克隆。
二、浅克隆与深克隆的区别
浅克隆:复制对象时仅仅复制对象本身,包括基本属性,但该对象的属性引用其他对象时,该引用对象不会被复制,即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象是同一个。
深克隆:复制对象本身的同时,也复制对象包含的引用指向的对象,即修改被克隆对象的任何属性都不会影响到克隆出来的对象。
例子如下:
class Person implements Cloneable{ private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } public void setAge(int age) { this.age = age; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}'; } @Override protected Person clone() throws CloneNotSupportedException { return (Person)super.clone(); //调用父类的clone方法 } }
测试代码:
public class CloneTest { public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(22,"LiLei"); Person newPerson = person.clone(); person.setAge(21); person.setName("HanMeimei"); System.out.println(person.toString()); System.out.println(newPerson.toString()); } }
测试结果:
Person{age=21, name='HanMeimei'}
Person{age=22, name='LiLei'}
即在克隆出新的对象后,修改被克隆对象的基本属性,并不会影响克隆出来的对象。但当被克隆的对象的属性引用其他对象时,此时会有不同的结果。
例子如下:
/** * 学生类 */ class Student implements Cloneable{ private String name; private Achievement achievement; //成绩 public Student(String name, Achievement achievement) { this.name = name; this.achievement = achievement; } public void setName(String name) { this.name = name; } public void setAchievement(Achievement achievement) { this.achievement = achievement; } public Achievement getAchievement() { return achievement; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", achievement=" + achievement + '}'; } @Override protected Student clone() throws CloneNotSupportedException { return (Student) super.clone(); } } /** * 成绩类 */ class Achievement implements Cloneable{ private float Chinese; private float math; private float English; public Achievement(float chinese, float math, float english) { Chinese = chinese; this.math = math; English = english; } public void setChinese(float chinese) { Chinese = chinese; } public void setMath(float math) { this.math = math; } public void setEnglish(float english) { English = english; } @Override public String toString() { return "Achievement{" + "Chinese=" + Chinese + ", math=" + math + ", English=" + English + '}'; } @Override protected Achievement clone() throws CloneNotSupportedException { return (Achievement) super.clone(); } }
测试代码:
public class CloneTest { public static void main(String[] args) throws CloneNotSupportedException { Achievement achievement = new Achievement(100,100,100); Student student = new Student("LiLei",achievement); // 克隆出一个对象 Student newStudent = student.clone(); // 修改原有对象的属性 student.setName("HanMeimei"); student.getAchievement().setChinese(90); student.getAchievement().setEnglish(90); student.getAchievement().setMath(90); System.out.println(newStudent); System.out.println(student); } }
测试结果:
Student{name='LiLei', achievement=Achievement{Chinese=90.0, math=90.0, English=90.0}}
Student{name='HanMeimei', achievement=Achievement{Chinese=90.0, math=90.0, English=90.0}}
以上现象表明,上述克隆方式为浅克隆,并不会克隆对象的属性引用的对象,当修改被克隆对象的成绩时,克隆出来的对象也会跟着改变,即两个对象的属性引用指向的是同一个对象。
但只要修改一下Student类中重写的clone()方法,即可实现深克隆。
修改代码如下:
@Override protected Student clone() throws CloneNotSupportedException { Student student = (Student) super.clone(); Achievement achievement = student.getAchievement().clone(); student.setAchievement(achievement); return student; }
测试结果:
Student{name='LiLei', achievement=Achievement{Chinese=100.0, math=100.0, English=100.0}}
Student{name='HanMeimei', achievement=Achievement{Chinese=90.0, math=90.0, English=90.0}}
即在Student类中的clone()方法中再克隆一次Achievement对象,并赋值给Student对象。
值得一提的是,上文所说的浅拷贝只会克隆基本数据属性,而不会克隆引用其他对象的属性,但String对象又不属于基本属性,这又是为什么呢?
这是因为String对象是不可修改的对象,每次修改其实都是新建一个新的对象,而不是在原有的对象上修改,所以当修改String属性时其实是新开辟一个空间存储String对象,并把引用指向该内存,而克隆出来的对象的String属性还是指向原有的内存地址,所以String对象在浅克隆中也表现得与基本属性一样。