Java 克隆
1.为什么要克隆?
新new一个不好吗?new一个的状态是初始值,如果改变了某个属性值,则需要通过相同的改变操作使得new出来的对象和现对象值相同。克隆可直接复制当前对象的任何值。并且初始化可能费时间比较多。克隆有浅克隆和深克隆。
2.浅克隆
需要重写Cloneable接口中的clone()方法。
package my_test; public class TestCopy { public static void main(String[] args) throws Exception{ Monkey m1=new Monkey(); m1.setId(66); Monkey m2=(Monkey) m1.clone(); Monkey m3=(Monkey)m2.clone(); m2.setId(99); Monkey m4=m1; System.out.println("m1.id="+m1.getId()+" getClass()="+m1.getClass()); System.out.println("m2.id="+m2.getId()+" getClass()="+m2.getClass()); System.out.println("m3.id="+m3.getId()+" getClass()="+m3.getClass()); System.out.println("m3.id="+m3.getId()+" getClass()="+m4.getClass()); System.out.println(m1==m2); System.out.println(m1==m4); System.out.println(m1); System.out.println(m2); System.out.println(m3); System.out.println(m4); } } class Monkey implements Cloneable { private int id; public void setId(int id) { this.id = id; } public int getId() { return id; } public Object clone() throws CloneNotSupportedException{ return super.clone(); } }
输出:
m1.id=66 getClass()=class my_test.Monkey
m2.id=99 getClass()=class my_test.Monkey
m3.id=66 getClass()=class my_test.Monkey
m3.id=66 getClass()=class my_test.Monkey
false
true
my_test.Monkey@15db9742
my_test.Monkey@6d06d69c
my_test.Monkey@7852e922
my_test.Monkey@15db9742
m2是通过m1克隆的,m3是通过m2克隆的,m4和m1指向同一个堆内存,再修改m2的值,通过返回toString()方法可知。m1、m2、m3地址各不相同,m1和m4地址相同,修改m2的值,不影响m1和m3,getClass()返回的类型相同。表明通过clone()方法克隆出的对象是与原对象是独立的,开辟了新的堆内存。
在Monkey类里添加引用类型Peach进行测试
package my_test; public class TestCopy { public static void main(String[] args) throws Exception{ Peach peach=new Peach(); peach.setName("猕猴桃"); Monkey m1=new Monkey(); m1.setId(66); m1.setP(peach); Monkey m2=(Monkey)m1.clone(); System.out.println("m1.id="+m1.getId()+" m1.p="+m1.getP()+" p.name="+m1.getP().getName()); System.out.println("m2.id="+m2.getId()+" m2.p="+m2.getP()+" p.name="+m2.getP().getName()); m1.setId(99); peach.setName("水蜜桃"); System.out.println("m1.id="+m1.getId()+" m1.p="+m1.getP()+" p.name="+m1.getP().getName()); System.out.println("m2.id="+m2.getId()+" m2.p="+m2.getP()+" p.name="+m2.getP().getName()); } } class Monkey implements Cloneable { private int id; private Peach p; public void setId(int id) { this.id = id; } public int getId() { return id; } public Object clone() throws CloneNotSupportedException{ return super.clone(); } public void setP(Peach p) { this.p=p; } public Peach getP() { return p; } } class Peach{ private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } }
输出:
m1.id=66 m1.p=my_test.Peach@15db9742 p.name=猕猴桃
m2.id=66 m2.p=my_test.Peach@15db9742 p.name=猕猴桃
m1.id=99 m1.p=my_test.Peach@15db9742 p.name=水蜜桃
m2.id=66 m2.p=my_test.Peach@15db9742 p.name=水蜜桃
m2是m1的克隆对象,m1和m2相互独立,但是 类中的引用类peach 是同一片内存,所以peach内容一改,m1和m2中的peach都改。
通过Clone()方法实现的浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;
如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时 只 复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。即复制不完全,不够深刻。
3.深克隆
要完完全全深深刻刻地全部复制,也可通过Clone()方法在浅复制中嵌套浅复制。
package my_test; public class TestCopy { public static void main(String[] args) throws Exception{ Peach peach=new Peach(); peach.setName("猕猴桃"); Monkey m1=new Monkey(); m1.setId(66); m1.setP(peach); Monkey m2=(Monkey)m1.clone(); System.out.println("m1.id="+m1.getId()+" m1.p="+m1.getP()+" p.name="+m1.getP().getName()); System.out.println("m2.id="+m2.getId()+" m2.p="+m2.getP()+" p.name="+m2.getP().getName()); m1.setId(99); peach.setName("水蜜桃"); System.out.println("m1.id="+m1.getId()+" m1.p="+m1.getP()+" p.name="+m1.getP().getName()); System.out.println("m2.id="+m2.getId()+" m2.p="+m2.getP()+" p.name="+m2.getP().getName()); } } class Monkey implements Cloneable { private int id; private Peach p; public void setId(int id) { this.id = id; } public int getId() { return id; } public void setP(Peach p) { this.p=p; } public Peach getP() { return p; } public Object clone() throws CloneNotSupportedException{//修改猴子类中的Clone()方法 Monkey monkey=null; try { monkey=(Monkey)super.clone();//先实现一下浅复制 } catch (CloneNotSupportedException e) { e.printStackTrace(); } monkey.p=(Peach)p.Clone();//深复制 = 浅复制 套 浅复制 return monkey; } } class Peach implements Cloneable{//桃子类也实现来实现Clone()方法 private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } public Object Clone() { Peach peach=null; try { peach = (Peach)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return peach; } }
输出:
m1.id=66 m1.p=my_test.Peach@15db9742 p.name=猕猴桃
m2.id=66 m2.p=my_test.Peach@6d06d69c p.name=猕猴桃
m1.id=99 m1.p=my_test.Peach@15db9742 p.name=水蜜桃
m2.id=66 m2.p=my_test.Peach@6d06d69c p.name=猕猴桃
此时改了m1中的p,不影响m2中的p,实现了完全复制。类中类内存也相互独立。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
用Clone()实现克隆的问题所在:如果多层引用类型,岂不是一层套一层套到天亮,为此有其他方法(序列化)可以实现深克隆。
package my_test; import java.io.*; public class TestCopy { public static void main(String[] args) throws Exception{ Peach peach=new Peach(); peach.setName("猕猴桃"); Monkey m1=new Monkey(); Monkey m2=null; m1.setId(66); m1.setP(peach); try { m2=(Monkey)m1.deepClone(); } catch (Exception e) { e.printStackTrace(); } System.out.println("1 m1.id="+m1.getId()+" m1.p="+m1.getP()+" p.name="+m1.getP().getName()); System.out.println("2 m2.id="+m2.getId()+" m2.p="+m2.getP()+" p.name="+m2.getP().getName()); m1.setId(99); peach.setName("水蜜桃"); System.out.println("3 m1.id="+m1.getId()+" m1.p="+m1.getP()+" p.name="+m1.getP().getName()); System.out.println("4 m2.id="+m2.getId()+" m2.p="+m2.getP()+" p.name="+m2.getP().getName()); } } class Monkey implements Serializable { private int id; private Peach p; public void setId(int id) { this.id = id; } public int getId() { return this.id; } public void setP(Peach p) { this.p=p; } public Peach getP() { return this.p; } public Object deepClone() throws IOException,ClassNotFoundException,OptionalDataException { //将对象写入流中 ByteArrayOutputStream bao=new ByteArrayOutputStream(); ObjectOutputStream oos=new ObjectOutputStream(bao); oos.writeObject(this); //将对象从流中取出 ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray()); ObjectInputStream ois=new ObjectInputStream(bis); return (ois.readObject()); } } class Peach implements Serializable{//类中的成员类也需要 实现这个接口 private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } }
输出:
m1.id=66 m1.p=my_test.Peach@55f96302 p.name=猕猴桃
m2.id=66 m2.p=my_test.Peach@214c265e p.name=猕猴桃
m1.id=99 m1.p=my_test.Peach@55f96302 p.name=水蜜桃
m2.id=66 m2.p=my_test.Peach@214c265e p.name=猕猴桃
通过序列化实现深克隆,成员的引用类型太多则不需要像Clone()那样实现太多方法。
实现步骤:
- 需要克隆的类无论是主类还是成员类都要实现Serializable接口
- 写一个deepClone()方法搞定一切,代码照抄,异常抛出照抄