潘瑞峰的个人博客

JAVA深拷贝和浅拷贝

潘瑞峰 copy拷贝

赋值

一般情况下的赋值都是地址引用的传递(引用和指针是有区别的,在java里面没有指针,只有引用,引用是不能被修改的;而指针是可以修改的,如在C++里面有pointer++)

所以在使用JAVA的时候,对于赋值对象的修改需要小心。比如在某些框架下的依赖注入,对于注入的对象,在使用过程中,如果进行修改可能会得到不同的结果。

public class Test {
	
	public String name;

	public static void main(String[] args) {
		//定义变量test,并将其名字定义为dog
		Test test = new Test();
		test.name = "dog";
		
		//将test赋值给test2,修改其名字为cat
		Test test2 = test;
		test2.name = "cat";
		
		System.out.println(test.name);
		System.out.println(test2.name);
	}

}

其输出结果为

cat
cat

可以看出test和test1引用的是内存中的同一个对象,修改任何一个引用,都会影响其他引用。

浅拷贝

浅拷贝是指拷贝对象本身(包括对象中的基本变量),不拷贝对象中的引用。在JAVA中,实现浅拷贝的方法是实现Cloneable接口

举个例子

public class Test implements Cloneable {
	
	public String name;
	public List<String> values = new ArrayList<String>();
	
	public Test clone() {
		Test testCloned = null;
        try {
        	testCloned = (Test) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
 
        return testCloned;
	}

	public static void main(String[] args) {
		//定义变量test
		Test test = new Test();
		test.name = "dog";
		test.values.add(0, "dog value");
		
		//将test克隆给test2,并修改test2的内容
		Test test2 = test.clone();
		test2.name = "cat";
		test2.values.set(0, "cat value");
		
		System.out.println(test.name + ":" + test.values.get(0));
		System.out.println(test2.name + ":" + test2.values.get(0));
		
	}
	
}

其结果为

dog:cat value
cat:cat value

可以看出在浅拷贝中,修改基本变量name被拷贝了,所以修改name不会影响其他对象;而List属性则没有被拷贝,所有对象的List属性的值都改变了

深拷贝

1. 伪深拷贝

有一种假的深拷贝,它的方法是对所有涉及到的非基本类型变量都实现Cloneable接口。

比如上面的例子,做法就是重写List对象,让它也实现Cloneable接口,并且修改Test实现的clone的函数。

testCloned = (Test) super.clone();
testCloned.values = (List) values.clone();

为什么说是伪克隆呢?因为如果List对象里面也有非基本类型变量,那么这些非基本变量类型不会被克隆。

比如:假设重写的List里面有对象AClass,用上面的方法,AClass是不会被拷贝的。

这种伪深拷贝的关键是覆盖拷贝

2. 深拷贝

序列化(串行化)是深拷贝的一个方法。

过程:内存对象 -> 序列化 -> 新的内存对象。 这样前后两个对象的内容是一致的,但是在内存中的引用不一样。

序列化的过程如下:

public static <T extends Serializable> T clone(T obj) {
	T clonedObj = null;
	try {
		//将对象变成序列(序列号)
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		ObjectOutputStream objOutput = new ObjectOutputStream(output);
		objOutput.writeObject(obj);
		objOutput.close();
		
		//将序列还原成对象(反序列化)
		ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
		ObjectInputStream objInput = new ObjectInputStream(input);
			
		clonedObj = (T) objInput.readObject();
		
		objInput.close();
	} catch (IOException | ClassNotFoundException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
		
	return clonedObj;
		
}

在使用这个clone函数的时候,需要让原有对象实现Serializable接口(从clone函数的参数也能看出)。

public class Test implements Serializable {

	private static final long serialVersionUID = -2386382139170837795L;
	public String name;
	public List<String> values = new ArrayList<String>();

	public static void main(String[] args) {
		//定义变量test
		Test test = new Test();
		test.name = "dog";
		test.values.add(0, "dog value");
		
		//将test克隆给test2
		Test test2 = clone(test);
		test2.name = "cat";
		test2.values.set(0, "cat value");
		
		System.out.println(test.name + ":" + test.values.get(0));
		System.out.println(test2.name + ":" + test2.values.get(0));
		
	}
	
}

结果为:

dog:dog value
cat:cat value
潘瑞峰
五花马,千金裘,呼儿将出换美酒,与尔同销万古愁。