Java中的深拷贝和浅拷贝
首先应该清楚Java中的栈内存和堆内存的区别,比如new出来的对象Person p = new Person()其中p并不是真正的对象,p只是代表的引用(地址),真正的对象存在于堆内存中。
一说起拷贝很多人第一反应该是赋值吧,的确复制也是一种拷贝方式,比如基本数据类型的变量int a = b;这个就达到了在栈内存中拷贝的效果。但是不要忘了堆内存,正如前面所说的情况:p在栈内存中,而p引用的对象实体在堆内存中,如果还是直接以Person p1 = p这样的形式拷贝的话可能就要出问题啦!通过p1访问的Person成员变量,如果在此之前p已经修改过p1需要访问的变量,那么p1访问的就是p修改过的成员变量,也就是说p与p1共享一个对象,并没有达到拷贝的效果。
验证程序如下:
public class Temp {
public static void main(String[] args) {
Person p = new Person("张三");
Person p1 = p;
System.out.println(p1.getName());
p.setName("李四");
System.out.println(p1.getName());
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
运行结果:
张三
李四
Java的Object类中提供了clone()方法,此方法就是用来拷贝操作的,但它是一种浅拷贝,如果要达到深拷贝效果的话,还得添加一些后续操作。下面我们来详细讨论一下。
深拷贝与浅拷贝的区别是:深拷贝需要在内存中再开辟一部分空间来存放对象,与之前的对象完全独立,没有任何共享的区域。
代码示例:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(20, "脚");
Person p2 = (Person) p1.clone();
System.out.println(p1);
System.out.println(p2);
}
}
class Person implements Cloneable {
private int age;
private WalkWay way ;
public Person(int age, String way) {
this.age = age;
this.way = new WalkWay(way);
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public WalkWay getWalkWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class WalkWay {
private String way;
public WalkWay(String way) {
this.way = way;
}
public void setWay(String way) {
this.way = way;
}
public String getWay() {
return this.way;
}
}
运行结果:
Person@2a139a55
Person@15db9742
由以上代码的运行结果可以看出p1与p2所引用的对象在堆内存中处于不同的两块区域中,但是它们真的完全独立吗? 下面我们来验证一下,将上面给程序main方法稍作修改,代码如下:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(20, "脚");
Person p2 = (Person) p1.clone();
System.out.println(p1.getWalkWay() == p2.getWalkWay());
p1.getWalkWay().setWay("手");
p1.setAge(21);
System.out.println("p1 " + p1.getWalkWay().getWay() + " " + p1.getAge());
System.out.println("p2 " + p2.getWalkWay().getWay() + " " + p2.getAge());
}
}
运行结果:
true
p1 手 21
p2 手 20
很明显p1和p2并没有实现完全独立,而且我们发现其中基本数据类型是完全独立的,p1与p2之间没有影响;问题就出在引用类型上,p1将自己的way修改为”手”,p2的way也跟着变了,说明它们引用的是同一个对象。
因此我们可以判断出Object类中的clone()方法本身实现的是浅拷贝,那如何进行深拷贝呢?先看下面的代码示例:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(20, "脚");
Person p2 = (Person) p1.clone();
System.out.println(p1.getWalkWay() == p2.getWalkWay());
p1.getWalkWay().setWay("手");
p1.setAge(21);
System.out.println("p1 " + p1.getWalkWay().getWay() + " " + p1.getAge());
System.out.println("p2 " + p2.getWalkWay().getWay() + " " + p2.getAge());
}
}
class Person implements Cloneable {
private int age;
private WalkWay way ;
public Person(int age, String way) {
this.age = age;
this.way = new WalkWay(way);
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public WalkWay getWalkWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.way = (WalkWay) p.way.clone();
return p;
}
}
class WalkWay implements Cloneable {
private String way;
public WalkWay(String way) {
this.way = way;
}
public void setWay(String way) {
this.way = way;
}
public String getWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
运行结果:
false
p1 手 21
p2 脚 20
如果要clone的对象中含有引用类型的变量,那么既要对要克隆对象的类实现Cloneable接口,还要对该类中的引用类型的类实现Cloneable接口,正如上面的示例,对Person和WalkWay都要implements Cloneable,并且都要重写clone()方法,对于WalkWay而言只需要调用父类的clone()方法即super.clone(),然后返回就可以了。
可能有人会说,String也是引用类型啊,为什么不对它实现 Cloneable接口呢?String类是final修饰的,不能被继承,其实我们可以用其他方式来对String进行深拷贝。
修改如下:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(20, "脚");
Person p2 = (Person) p1.clone();
System.out.println(p1.getWalkWay().getWay() == p2.getWalkWay().getWay());
}
}
class Person implements Cloneable {
private int age;
private WalkWay way ;
public Person(int age, String way) {
this.age = age;
this.way = new WalkWay(way);
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public WalkWay getWalkWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.way = (WalkWay) p.way.clone();
return p;
}
}
class WalkWay implements Cloneable {
private String way;
public WalkWay(String way) {
this.way = way;
}
public void setWay(String way) {
this.way = way;
}
public String getWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
WalkWay walkWay = (WalkWay) super.clone();
walkWay.way = new String(way);
return walkWay;
}
}
还有一种方式:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(20, "脚");
Person p2 = (Person) p1.clone();
System.out.println(p1.getWalkWay().getWay() == p2.getWalkWay().getWay());
}
}
class Person implements Cloneable {
private int age;
private WalkWay way ;
public Person(int age, String way) {
this.age = age;
this.way = new WalkWay(way);
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public WalkWay getWalkWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.way = (WalkWay) p.way.clone();
return p;
}
}
class WalkWay implements Cloneable {
private String way;
public WalkWay(String way) {
this.way = new String(way);
}
public void setWay(String way) {
this.way = new String(way);
}
public String getWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
WalkWay walkWay = (WalkWay) super.clone();
// walkWay.way = new String(way);
return walkWay;
}
}
这两种方式可以说是利用String类型数据在内存中的存储原理来实现的。