Java对象的深拷贝和浅拷贝

562 阅读5分钟

很多优秀的同学已经了解浅拷贝和深拷贝的区别了,这边文章主要是实现深拷贝为主,虽然这类文章也很多,但是这个就是当做自己学习路上的一个脚印。写的不好,希望大佬勿喷。

1、深拷贝和浅拷贝的区别

深拷贝和浅拷贝的主要区别在于引用类型的对象的拷贝,浅拷贝只会拷贝引用对象的地址,原对象和拷贝对象实际上指向的都是同一个引用类型的地址。所以拷贝对象修改这个引用类型的对象时,原对象也会跟着改变。

浅拷贝的拷贝结果:

深拷贝就是拷贝的同时,引用类型的对象,也会跟着一起拷贝,拷贝对象和引用对象,完全不相关。

深拷贝的结果:

* 注意: 此处有一个点需要记住:String类型,String类型本身虽然也是引用类型,但是由于String类型底层是final类型的,同时因为有字符串常量池的存在,每次修改String类型的字段,实际上都会生成一个新的字符串常量,因此String类型虽然是引用类型,但是无须单独考虑

2、如何实现一个深拷贝

Java里面,浅拷贝的实现,实现Cloneable接口,然后将clone方法重写,并将修饰符由protected改为public,方便外部调用。

我们这边主要讲深拷贝,深拷贝一般有两种实现方式:

  • 1、实现Cloneable接口,然后重写clone方法,在每个引用对象处,再使用一次clone的拷贝方法;
  • 2、使用序列化的方式,将原对象转换为流,再将流转换成需要的对象。

下面分别会分别对两种拷贝方式进行实现:

//@Data
@Setter
@Getter
public class BaseDeepObject implement Cloneable{

	private int id;

	private String name;

	public BaseDeepObject(int id, String name) {
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

//@Data
@Setter
@Getter
public class DeepCloneObject implements Cloneable {

	private Integer id;

	private String name;

	private BaseDeepObject baseDeepObject;

	public DeepCloneObject(Integer id, String name, BaseDeepObject baseDeepObject) {
		this.id = id;
		this.name = name;
		this.baseDeepObject = baseDeepObject;
	}

	/**
	 * 深拷贝
	 *
	 * @return
	 * @throws CloneNotSupportedException
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {
		DeepCloneObject deepCloneObject = (DeepCloneObject) super.clone();
		deepCloneObject.baseDeepObject = (BaseDeepObject) baseDeepObject.clone();

		return deepCloneObject;
	}
}

打印的结果:
BaseDeepObject baseDeepObject = new BaseDeepObject(1111, "引用对象");
DeepCloneObject deepCloneObject =new DeepCloneObject(12345, "深拷贝原对象", baseDeepObject);

try {
	DeepCloneObject cloneObject = (DeepCloneObject) deepCloneObject.clone();

	System.out.println(deepCloneObject == cloneObject);
	System.out.println(deepCloneObject.equals(cloneObject));
	System.out.println("原对象hash值" + deepCloneObject.hashCode());
	System.out.println("真正的内存地址" + System.identityHashCode(deepCloneObject));

	System.out.println("克隆对象的hash值" + cloneObject.hashCode());
	System.out.println("拷贝对象真正的内存地址" + System.identityHashCode(cloneObject));

	System.out.println("原对象里面的引用对象hash值" + deepCloneObject.getBaseDeepObject().hashCode());
	System.out.println("克隆对象里面的引用对象hash值" + cloneObject.getBaseDeepObject().hashCode());

} catch (CloneNotSupportedException e) {
	e.printStackTrace();
}

false
false
原对象hash值1376790572
真正的内存地址1376790572
克隆对象的hash值1404601430
拷贝对象真正的内存地址1404601430
原对象里面的引用对象hash值1095240931
克隆对象里面的引用对象hash值1819274917

因此可以看出,深拷贝之后,引用对象的地址也已经改变,源对象和拷贝对象完全是独立的存在了。

此处肯定有小伙伴看到了,我不仅仅打印出来了hashCode,同时也取了真正的内存地址,这个主要是使用lombok的@Data注解导致的,@Data注解里面包含了@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode 这5个注解,会对hashCode和equals进行重写,因此取到的hashCode是有可能相同,但是 == 判断的时候,是false,equal判断的时候是true。感兴趣的小伙伴可以自己尝试一下@Data注解带来的比较。

*** 注:== 判断比较的内存地址,equal先比较hashCode,再比较每个字段的值

方式二:
public DeepCloneObject deepClone() {
DeepCloneObject newObject = null;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
    // 使用输出流,将对象信息写入到流里面,做持久化
	ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
	outputStream.writeObject(this);
	outputStream.flush();
    // 从流里面,把信息读取出来,并重新赋值给一个新的对象
	ByteArrayInputStream bin = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
	ObjectInputStream objectInputStream = new ObjectInputStream(bin);
	newObject = (DeepCloneObject) objectInputStream.readObject();

	outputStream.close();
	objectInputStream.close();
} catch (IOException | ClassNotFoundException e) {
	e.printStackTrace();
}
return newObject;
}

BaseDeepObject baseDeepObject = new BaseDeepObject(1111, "引用对象");
DeepCloneObject deepCloneObject =
		new DeepCloneObject(12345, "深拷贝原对象", baseDeepObject);

DeepCloneObject cloneObject = deepCloneObject.deepClone();

System.out.println(deepCloneObject == cloneObject);
System.out.println(deepCloneObject.equals(cloneObject));
System.out.println("原对象hash值" + deepCloneObject.hashCode());
System.out.println("真正的内存地址" + System.identityHashCode(deepCloneObject));

System.out.println("克隆对象的hash值" + cloneObject.hashCode());
System.out.println("拷贝对象真正的内存地址" + System.identityHashCode(cloneObject));

System.out.println("原对象里面的引用对象hash值" + deepCloneObject.getBaseDeepObject().hashCode());
System.out.println("克隆对象里面的引用对象hash值" + cloneObject.getBaseDeepObject().hashCode());

false
false
原对象hash值407323310
真正的内存地址407323310
克隆对象的hash值2069577154
拷贝对象真正的内存地址2069577154
原对象里面的引用对象hash值511928860
克隆对象里面的引用对象hash值88867683

方法二主要是把对象做序列化处理,将数据放到流里面做持久化,然后再读出来,这样的话,肯定是一个新的对象。

深拷贝和浅拷贝暂时就介绍到这边,感谢各位小伙伴的观看,有问题的话,可以在留言区留言,大家相互学习。