关于对象浅拷贝,序列化实现对象的拷贝

  

           我们知道一个类实现了Cloneable接口就表示它具备了拷贝的能力,如果再覆写clone方法就完全具备拷贝能力。拷贝是在内存中进行的,所以在性能方面比直接通过new生成对象要快的多,特别在大对象生成上,这会使性能提升非常显著.但是对象拷贝也有一个比较容易比较忽略的问题:浅拷贝也叫影子拷贝存在对象属性拷贝不彻底的问题。

1.浅拷贝

看如下代码:

public class Client {
	public static void main(String[] args) {
		Person p =new Person("父亲");
	
		Person s1 = new Person("大儿子",p);
		Person s2 = s1.clone();

		s2.setName("小儿子");

		System.out.println(s1.getName()+" 的父亲是 "+ s1.getFather() .getName());
		System.out.println(s2.getName()+" 的父亲是 "+ s2.getFather() .getName());
	}
}
public class Person implements Cloneable {
	
	private String name;
	private Person father;

	public Person(String name) {
		super();
		this.name = name;
	}

	public Person(String name, Person father) {
		super();
		this.name = name;
		this.father = father;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Person getFather() {
		return father;
	}

	public void setFather(Person father) {
		this.father = father;
	}

	@Override
	protected Person clone() throws CloneNotSupportedException {
		Person p = null;
		try {

			p = (Person) p.clone();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return p;
	}
}

          程序中,我们描述了一个这样的场景:一个父亲有两个儿子,大小儿子所以同根同种,所以小儿子对象就通过拷贝大儿子对象来生成,运行输出结果如下:

大儿子 的父亲是 父亲
小儿子 的父亲是 父亲

这样没毛病!

       突然有一天,父亲心血来潮想让大儿子认个干爹,也就是大儿子的父亲名称需要重新设置下

public class Client {
	public static void main(String[] args) {
		Person p =new Person("父亲");
	
		Person s1 = new Person("大儿子",p);
		Person s2 = s1.clone();

		s2.setName("小儿子");
                //大儿子认个干爹
		s1.getFather().setName("干爹");

		System.out.println(s1.getName()+" 的父亲是 "+ s1.getFather() .getName());
		System.out.println(s2.getName()+" 的父亲是 "+ s2.getFather() .getName());
	}
}

运行结果:

大儿子 的父亲是 干爹
小儿子 的父亲是 干爹

 看输出结果,小儿子父亲也变成干爹了。

              出现这个问题的原因是在于clone方法,我们知道所有类都继承Object,Object提供了一个对象拷贝的默认方法,即上面代码中的super.clone()方法,但这个方法是有缺陷的,他提供了一种浅拷贝的方式,也就是说它并不会把对象所有属性全部拷贝一份,而有选择性的拷贝,它的拷贝规则如下:

    (1)基本类型

             如果变量是基本类型,则拷贝其值,比如int,float等。

    (2)对象

              如果变量是一个实例对象,则拷贝地址引用,也就是说此时新拷贝的对象与原有对象共享该实例变量,不受访问权限的限制。这在java是很疯狂的,因为它突破了访问权限的定义,一个private修饰的变量,竟可以被两个不同的实例对象访问,这让java访问权限体系情何以堪!

     (3)String字符串

               这个比较特殊,拷贝的也是一个新地址,是个引用,但是在修改时,他会从字符串池中重新生成新的字符串,原有字符串对象保持不变,在此处我们可以认为String是一个基本类型。

       明白了这三个规则,上面的例子就很清晰了,小儿子对象是通过拷贝大儿子产生的,其父亲是同一个人,也就是同一个对象,大儿子修改了父亲名称,小儿子也跟随修改了---------想要正确修改clone方法的代码如下

public Person clone() throws CloneNotSupportedException {
		Person p = null;
		try {
			p = (Person) p.clone();
			p.setFather(new Person(p.getFather().getName()));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return p;
}

  2.浅拷贝

            对于浅拷贝的问题,实现了Cloneable接口就具备了拷贝能力,那我们来思考一个问题,如果一个项目有大量的对象通过拷贝生成的我们应该怎么处理?每一个类都谢一个clone方法,并且还要深拷贝?想想工作量就巨大。

有一种更好的方法,可以通过序列化方式来处理,在内存中通过字节流的拷贝来实现,也就是把母对象写到一个字节流中,再从字节流中将其读取出来,这样就可以重建一个对象了,该对象与母对象直接不存在引用共享的问题,也就相当于深拷贝一个对象,代码如下:

public class Person implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -2273893604786942028L;
	
	private String name;
	private Person father;

	public Person(String name) {
		super();
		this.name = name;
	}

	public Person(String name, Person father) {
		super();
		this.name = name;
		this.father = father;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Person getFather() {
		return father;
	}

	public void setFather(Person father) {
		this.father = father;
	}
}
public class Client {
	public static void main(String[] args) {
		Person p =new Person("父亲");
		
		Person s1 = new Person("大儿子",p);
		Person s2 = CloneUtils.clone(s1);
		s2.setName("小儿子");
		s1.getFather().setName("干爹");
		System.out.println(s1.getName()+" 的父亲是 "+ s1.getFather() .getName());
		System.out.println(s2.getName()+" 的父亲是 "+ s2.getFather() .getName());
	}
}

猜你喜欢

转载自blog.csdn.net/hqbootstrap1/article/details/81293898