JAVA实现深复制: clone()方法及序列化
Java语言取消了指针的概念,导致了许多程序员在编程中常常忽略了对象与引用的区别。Java不能通过简单的赋值来解决对象复制的问题,在开发过程中,常常要应用clone()方法来复制对象。比如函数参数类型是自定义的类时,此时便是引用传递而不是值传递。以下是一个小例子:
Java代码
1. public class A {
2. public String name;
3. }
Java代码
1. public class testClone {
2. public void changeA(A a){
3. a.name="b";
4. }
5. public void changInt(int i){
6. i=i*2+100;
7. }
8. public static void main(String[] args) {
9. // TODO Auto-generated method stub
10. testClone test=new testClone();
11. A a=new A();
12. a.name="a";
13. System.out.println("before change : a.name="+a.name);
14. test.changeA(a);
15. System.out.println("after change : a.name="+a.name);
16. int i=1;
17. System.out.println("before change : i="+i);
18. test.changInt(i);
19. System.out.println("after change : i="+i);
20. }
21. }
此时输出的结果是:
1. before change : a.name=a
2. after change : a.name=b
3. before change : i=1
4. after change : i=1
从这个例子知道Java对对象和基本的数据类型的处理是不一样的。在Java中用对象的作为入口参数的传递则缺省为"引用传递",也就是说仅仅传递了对象的一个"引用",这个"引用"的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时,实质上就是在对这个对象的直接操作。
除了在函数传值的时候是"引用传递",在任何用"="向对象变量赋值的时候都是"引用传递",如:
Java代码
1. A a1=new A();
2. A a2=new A();
3. a1.name="a1";
4. a2=a1;
5. a2.name="a2";
6. System.out.println("a1.name="+a1.name);
7. System.out.println("a2.name="+a2.name);
此时输出的结果是:
1. a1.name=a2
2. a2.name=a2
如果我们要用a2保存a1对象的数据,但又不希望a2对象数据被改变时不影响到a1。实现clone()方法是其一种最简单,也是最高效的手段。下面我们来实现A的clone方法
Java代码
1. public class A implements Cloneable {
2. public String name;
3. public Object clone() {
4. A obj = null;
5. try {
6. obj = (A) super.clone();
7. } catch (CloneNotSupportedException e) {
8. e.printStackTrace();
9. }
10. return obj;
11. }
12. }
首先要实现Cloneable接口,然后在重载clone方法,最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。
Java代码
1. A a1=new A();
2. A a2=new A();
3. a1.name="a1";
4. a2=(A)a1.clone();
5. a2.name="a2";
6. System.out.println("a1.name="+a1.name);
7. System.out.println("a2.name="+a2.name);
此时输出的结果是:
1. a1.name=a1
2. a2.name=a2
当Class A成员变量类型是java的基本类型时(外加String类型),只要实现如上简单的clone(称影子clone)就可以。但是如果Class A成员变量是数组或复杂类型时,就必须实现深度clone。
Java代码
1. public class A implements Cloneable {
2. public String name[];
3. public A(){
4. name=new String[2];
5. }
6. public Object clone() {
7. A obj = null;
8. try {
9. obj = (A) super.clone();
10. } catch (CloneNotSupportedException e) {
11. e.printStackTrace();
12. }
13. return obj;
14. }
15. }
测试Java代码
1. A a1=new A();
2. A a2=new A();
3. a1.name[0]="a";
4. a1.name[1]="1";
5. a2=(A)a1.clone();
6. a2.name[0]="b";
7. a2.name[1]="1";
8. System.out.println("a1.name="+a1.name);
9. System.out.println("a1.name="+a1.name[0]+a1.name[1]);
10. System.out.println("a2.name="+a2.name);
11. System.out.println("a2.name="+a2.name[0]+a2.name[1]);
输出结果:
Java代码
1. a1.name=[Ljava.lang.String;@17943a4
2. a1.name=b1
3. a2.name=[Ljava.lang.String;@17943a4
4. a2.name=b1
a1.name,a2.name都是@17943a4,也就是说影子clone对name数组只是clone他们的地址!解决该办法是进行深度clone。
Java代码
1. public Object clone() {
2. A obj = null;
3. try {
4. obj = (A) super.clone();
5. obj.name=(String[])name.clone();//这是实现方式
6. } catch (CloneNotSupportedException e) {
7. e.printStackTrace();
8. }
9. return obj;
10. }
此时输出结果是:
Java代码
1. a1.name=[Ljava.lang.String;@17943a4
2. a1.name=a1
3. a2.name=[Ljava.lang.String;@480457
4. a2.name=b1
需要注意的是Class A存在更为复杂的成员变量时,如Vector等存储对象地址的容器时,就必须clone彻底。
Java代码
1. public class A implements Cloneable {
2. public String name[];
3. public Vector<B> claB;
4. public A(){
5. name=new String[2];
6. claB=new Vector<B>();
7. }
8. public Object clone() {
9. A obj = null;
10. try {
11. obj = (A) super.clone();
12. obj.name==(String[])name.clone();//深度clone
13. obj.claB=new Vector<B>();//将clone进行到底
14. for(int i=0;i<claB.size();i++){
15. B temp=(B)claB.get(i).clone();//当然Class B也要实现相应clone方法
16. obj.claB.add(temp);
17. }
18. } catch (CloneNotSupportedException e) {
19. e.printStackTrace();
20. }
21. return obj;
22. }
23. }
利用序列化化来做深复制
将对象以流形式写入一个字节数组,再写出
如下为深复制源代码。
public Object deepClone()
{
//将对象写到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。
class Professor implements Serializable
{
String name;
int age;
Professor(String name,int age)
{
this.name=name;
this.age=age;
}
}
class Student implements Serializable
{
String name;//常量对象。
int age;
Professor p;//学生1和学生2的引用值都是一样的。
Student(String name,int age,Professor p)
{
this.name=name;
this.age=age;
this.p=p;
}
public Object deepClone() throws IOException,
OptionalDataException,ClassNotFoundException
{
//将对象写到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
}
public static void main(String[] args)
{
Professor p=new Professor("wangwu",50);
Student s1=new Student("zhangsan",18,p);
Student s2=(Student)s1.deepClone();
s2.p.name="lisi";
s2.p.age=30;
System.out.println("name="+s1.p.name+","+"age="+s1.p.age); //学生1的教授不改变。
}